放浪エンジニアのLog

少しハッピーになれる開発

Playframework (java) で環境によってDBの接続先を変える方法

今まではRailsの記事を書いていましたが、最近は Java の Playframework にはまっており、今回はその Play の環境にまつわるお話です。

よく環境毎に接続するDBが違う場合があり、そのために読み込むconfファイルを動的に変えたいシチュエーションなどがあると思います。
(開発、テスト、本番環境などでDBが変わる場合)


今回は「サーバのホスト名」などによって環境を判別し、その環境によって読み込むDBの設定ファイルを動的に変える方法を記載します。
(相変わらずにっちな需要だと思いますが….)

実現方法

方法としてはアプリケーション起動時に読み込むConfファイルを変えます。
こう書いてしまうと、とても簡単ですね….

Play2.3までであれば GlobalSettings を継承したクラスを作成し、その中で onStart などが使えたので、そこに記載すればよかったのです。

※ Play 2.3までであれば
public class ApplicationGlobal extends GlobalSettings {

    @Override
    public void onStart(Appcliation app) {
        // ここに起動時の設定などを記載する。
    }


しかし、残念ながらこのような書き方は Play2.4 からはできなくなりました(GlobalSettingsが非推奨となったため)。

GlobalSettingsが使えなくなった理由

それは Play2.4からGuiceを利用したDIが利用可能になったため、そちらを使うことが推奨されたためです。

Play2.4からの実装方法

まずは前提条件から記載しておきます
■ 今回の想定

  • HostName に応じて「local環境」、「テスト環境」、「本番環境」のDB接続先を変える
    • HostName が ○○○ だったら本番環境用の conf/prod.conf
    • HostName が test-○○○ だったらテスト環境用の conf/test.conf
    • 上記以外のHostNameだったら conf/local.conf

まずはDB接続用の設定用confの作成を行います。
(記載忘れていましたが、DB は今回 MySQL を使用します)

- conf/prod.conf
- conf/test.conf
- conf/local.conf

# 環境に応じた値を記入
driver=com.mysql.jdbc.Driver
url="jdbc:mysql://○○○:3306/hoge-db"
username=UserName
password="Password"


- ApplicationModule.java

import javax.inject.Inject;
import com.google.inject.AbstractModule;
import play.api.inject.ApplicationLifecycle;
 
public class ApplicationModule extends AbstractModule {

 
    @Override
    protected void configure() {
        bind(ApplicationJobs.class).asEagerSingleton();
    }


    public static class ApplicationJobs {

        @Inject
        public ApplicationJobs(ApplicationLifecycle lifecycle) {

            // DBのConnection設定
            ConnectionFactory.getInstanse().init();

            // その他初回起動でしたいことがあれば記載する  
        }
    }
}

9行目
Guiceがモジュールに渡す Binderによって、オブジェクトの作成方法を Guiceに指示しています。
ここでは asEagerSingleton() を使って、起動時にシングルトンのインスタンスを生成させています。

- ConnectionFactory.java

import java.io.File;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.net.InetAddress;

import org.apache.commons.dbcp2.BasicDataSource;
import org.avaje.datasource.DataSourceConfig;

import com.avaje.ebean.EbeanServerFactory;
import com.avaje.ebean.config.ServerConfig;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;

public class ConnectionFactory {

    /** シングルトンインスタンス */
    private static ConnectionFactory instance = null;

    /** データソース設定 */
    BasicDataSource bds;

    public static ConnectionFactory getInstanse() {
        if (instance == null) {
            instance = new ConnectionFactory();
        }
        return instance;
    }

    public void init() {
        getConnection();
    }


    private void initBasicDataSource() {
        String hostName = InetAddress.getLocalHost().getHostName();
        Config dbConfig;

        if (hostName.equals("○○○")) {
            // 本番環境の設定
            dbConfig = ConfigFactory.load(ConfigFactory.parseFileAnySyntax(new File("conf/prod.conf")));
            
        } else if (gnaviEnv.equals("test-○○○")) {
            // テスト環境の設定
            dbConfig = ConfigFactory.load(ConfigFactory.parseFileAnySyntax(new File("conf/test.conf")));
        } else {
            // それ以外(local環境の設定)
            dbConfig = ConfigFactory.load(ConfigFactory.parseFileAnySyntax(new File("conf/local.conf"))));
        }

        DataSourceConfig dsConfig = new DataSourceConfig() {
            {
                setDriver(dbConfig.getString("driver"));
                setUrl(dbConfig.getString("url"));
                setUsername(dbConfig.getString("username"));
                setPassword(dbConfig.getString("password"));
                setCaptureStackTrace(true);
                // ここらへんは必要に応じて設定
                // setMaxAgeMinutes(○○);
                // setMaxConnections(○○);
            }
        };

        ServerConfig serverConfig = new ServerConfig() {
            {
                List<Class<?>> classList = new ArrayList<>();

                // Modelの追加
                classList.add(○○Model.class);
                classList.add(○○Model.class);

                loadFromProperties();
                setClasses(classList);
                // DDLの出力先
                setName("db/ddl");
                setDataSourceConfig(dsConfig);

                setDefaultServer(true);
                setRegister(true);
                // trueにするとDDDLが自動生成される
                setDdlGenerate(true);
                // trueにするとApplication起動時にデータがすべて削除されて、DDLから新規でテーブル作成しなおす
                setDdlRun(false);

            }
        };
        EbeanServerFactory.create(serverConfig);
    }
}

ConnectionFactory では純粋にDBのConnectionまわりの設定を行っております。
※ 注意ポイント: Evolutionsを使ってのModules読み込みをしないので、Modelファイルを追加する度に手動で classList.add を行い、Modelファイルの追加をしてあげる必要があります。
(本当はこれも自動で読み込めるようにしてあげた方がよいが、さくっとかけなかった…..)

Google 検索結果のスクレイピング

今回はRubyのプログラムで、google検索の検索結果のスクレイピングをやってみようと思います。

主に取得する情報は
URL
タイトル部
スニペット

(スニペット部分は簡単に言うと、下の赤い箇所です!) f:id:kno75:20170220110355p:plain

そもそもスクレイピングとは?

スクレイピングは色んな人が扱っているため、今更説明する必要はないかもしれませんが…

ウェブスクレイピング(英: Web scraping)とは、ウェブサイトから情報を抽出するコンピュータソフトウェア技術のこと。ウェブ・クローラー[1]あるいはウェブ・スパイダー[2]とも呼ばれる。

引用:ウェブスクレイピング - Wikipedia

まさにスクレイピングとは、↑のことです

ざっくりとしたイメージ

例えば「最近の流行りのダイエット」について知りたい!
果たして、他の人はどんなことをWeb上に公開しているのでしょうか? それはダイエットについての「方法」、「効果」、「商品」などなど様々な情報だと思います。
「最近の流行りのダイエット」という漠然としたキーワードでgoogle先生に検索をかけ、ランキング上位の結果を取得し、眺めたり分析やら何やらに使えるのではないでしょうか?

簡単にキーワードからトレンド取得をしたい!
google先生の検索結果の取得がしてみたい!!そんなノリで作りました。

処置の流れ

処理の流れはざっくり分けて、2 stepで行えます。
1. Mechanizeを使って、google先生に検索をかける
2. 検索結果のHTMLから必要な部分を抽出する

ざっくり書くとこんな感じです。 とても簡単ですね….

1. Mechanizeを使って、google先生を使って検索をかける

今回google先生の検索には、Mechanizeを使います。

gemの設定

gem 'mechanize'

とりあえず、google先生に検索をかける箇所を簡単に実装してみる

googleSearch.rb

1 require 'mechanize' 
2                         
3 class GoogleSearch
4   def snipet_scraping(keyword)
5     submit_keyword(keyword)
6   end
7 
8   private
9 
10   def submit_keyword(keyword)
11     agent = Mechanize.new   
12     agent.get('https://www.google.co.jp/')
13     agent.page.form_with(name: 'f') do |form|
14       form.q = keyword
15     end.submit
16   end
17 end
18 
19 result = GoogleSearch.new.snipet_scraping("最近の流行りのダイエット")
20 p result

簡単にmechanizeの解説です
・11〜12行目
Mechanizeのインスタンスを生成し、googleURIをセットしています。

・13〜15行目
google TOPから検索をかける時と同じ容量で作業すると思ってください。
inputボックスの form の name が “f” なので、まずはそれを設定してあげます。

agent.page.form_with(name: 'f')  

次に input box に検索キーワードの設定をするわけですが、input box の name は “q” です。

form.q = keyword

最後に15行目でサブミットして完了です。簡単ですね。

f:id:kno75:20170220153148p:plain

実行結果

$ruby googleSearch.rb

左が実際にgoogle先生に検索をかけた画像で、右がmechanizeを使って実行して得られた結果です。 f:id:kno75:20170220145819p:plain

googleSearch.rb の実行結果では、ページの全HTMLの情報が取得されているので、次のstepで必要な情報だけを取得していきます。

2. 検索結果がHTMLで返ってくるので、必要な部分を抽出する

とりあえずソースを先に貼って、その後解説していきましょう
googleSearch.rb

1  require 'mechanize' 
2                         
3  class GoogleSearch
4    def snipet_scraping(keyword)
5      submit_keyword(keyword)
6      @agent.page.search('div.g').map do |node|
7        title = node.search('a')
8        next if title.empty?
9        query = URI.decode_www_form(URI(title.attr("href")).query)
10       url = query[0][1]
11
12       snipped = node.search('div.s > span.st')
13       next if snipped.empty? || snipped.children.empty?
14       {
15         url: url,
16         title: expect_tag(title.children.to_html),
17         snipped: expect_tag(snipped.children.to_html)
18       }
19     end.reject do |list|
20       list.nil?
21     end
22   end 
23
24   private
25
26   def submit_keyword(keyword)
27      @agent = Mechanize.new   
28      @agent.get('https://www.google.co.jp/')
29      @agent.page.form_with(name: 'f') do |form|
30        form.q = keyword
31      end.submit
32    end
33
34   def expect_tag(str)
35     str.gsub(/(<b>|<\/b>|<br>|<\/br>|\R)/, '')
36   end
37 end
38 
39 result = GoogleSearch.new.snipet_scraping("最近の流行りのダイエット")
40 result.each do |value|
41   p "------------------------------"
42   p "URL     : #{value[:url]}"
43   p "Title   : #{value[:title]}"
44   p "Snipped : #{value[:snipped]}"
45 end

それでは解説していきましょ〜

・6行目

@agent.page.search('div.g').map do |node|  

「div.g」でsearchを行いループを回していますが、これはgoogle先生の1件の検索結果ごとにループを回すようにしています。
また、Mechanizeのsearchを行うと、結果がNokogiriのオブジェクトで返却されます。

f:id:kno75:20170220174832p:plain

なぜ div.g かって?

それはgoogle先生で検索した結果を、consoleから解読したらそうなりました…
もちろん、google先生が何かの気まぐれで、タグの構成などを変えられたら….  

・7行目

title = node.search('a')

検索結果のタイトル(見出し)部の取得をしています。
ご覧の通り、aタグなのです。

・8行目

next if title.empty?

ここでタイトルの空判定をしているのは、実は[ div.g ]で取得を行うと欲しい検索結果以外の情報も取れるため、それを除外しています。

・16〜17行目

title: expect_tag(title.children.to_html),
snipped: expect_tag(snipped.children.to_html)

ここではタイトル, スニペットの設定を行なっていますが、title, snippedから[ children.to_html.to_html ]を行なって、情報を抜き取っています。

・34〜36行目

def expect_tag(str)
  str.gsub(/(<b>|<\/b>|<br>|<\/br>|\R)/, '')
end

上のchildren.to_html.to_html で取得した情報はHTMLのため、タグが含まれています。
そのため、ここで改行、スペース等の不要なタグを削除しています。

実行結果

$ ruby googleSearch.rb

"------------------------------"
"URL     : http://dietbook.biz/tankisyuutyuudietrankingtop10-4647.html"
"Title   : 短期集中ダイエットにおすすめ!【ランキングTOP10】 | ダイエットブックBIZキャッシュ類似ページオー バーナイトダイエットの効果 ...水分だけダイエットの効果と無理 ..."
"Snipped : 短期集中でダイエットしたい方におすすめのダイエット方法をランキングでご紹介しています。私が選ぶダイエット方法T... ... 美容にも良く、ダイエット効果もあるので今流行りのダイエットで噂されています。 個人差も あり、1日ですので大幅な変化はありませんが ..."
"------------------------------"
"URL     : http://thylakoid.jp/geinou/"
"Title   : 2016年度版、あの芸能人も痩せたダイエット方法別まとめキャッシュ"
"Snipped : あの芸能人はちょっと前まで太ってたにもかかわらず、もうダイエットに成功しているけど、一体どうやっているんだろう? 芸能人は忙しい中、ダイエットに成功しているけど、どうせお金も時間もかけてるんでしょ? そう 思っている方は非常に多いかと思います。"
"------------------------------"
"URL     : https://allabout.co.jp/gm/gl/4032/"
"Title   : 流行のダイエットでやせる! [特集・ダイエット] All Aboutキャッシュ"
"Snipped : テレビや雑誌、書籍で続々と紹介される新しいダイエット方法の数々。本当に効果があるの?という疑問に迫り ... 流行のダイエットでやせる! テレビや雑誌、書籍で続々と紹介され ... 最近は低体温の人が増えているそうです。体温が低いと代謝が悪くなるので、 ..."
"------------------------------"
"URL     : https://laurier.press/i/E1459215567169"
"Title   : キーワードは\"食べて痩せる\"!? 2016年に流行るダイエットTOP3 ...キャッシュ"
"Snipped : 2016年3月30日 ... ですが、ダイエットを決意してもなかなか思い通りにはいかないもの。 そこで今回は 、株式会社クックパッド ダイエットラボが3月26日に開催した『ビューティーダイエットフェス 』で紹介された2016年に流行りそうなダイエットをご紹介します。"
"------------------------------"
"URL     : http://diet-tv.net/articles/tv-popular-diet"
"Title   : テレビで話題のダイエット特集!流行りに乗り遅れるな! | ヤセルモキャッシュテレビで今話題のダイエ ットって何があ ...スーパーで手に入るあの食材でこん ..."
"Snipped : 2016年8月9日 ... スーパーで簡単に手に入るあの食材や簡単エクササイズ!テレビで話題の血液型別ダイ エット方法で美ボディを目指しましょう!"
"------------------------------"
"URL     : https://matome.naver.jp/odai/2143278518712676001"
"Title   : 最近流行りのダイエットの中でも変わり種ダイエットが色々すごい件 ...キャッシュ"
"Snipped : 最近流行りのダイエットの中でも変わり種ダイエットが色々すごい件. 美の追求が昔より強力になったのか、近年昔からの定番ダイエットの他、色々個性的なダイエット方法が増えてきています。 更新日: 2017年02月08日. moepapaさん ..."
"------------------------------"
"URL     : https://mdpr.jp/diet/detail/1553707"
"Title   : 【2016年ヒット予測/ダイエットトレンド】今年流行のダイエット法は?日本 ...キャッシュ"
"Snipped : ダイエット/モデルプレス=1月3日】2015年もたくさんのダイエット方法が流行し、モデルプレスでもご紹介してきました。では、2016年はどんなダイエット方法が来るのでしょうか?もう既にジワジワ来ているものもあるよ うですね。そこで今回は、2016年に ..."
"------------------------------"
"URL     : http://one-more-value.com/%E3%83%80%E3%82%A4%E3%82%A8%E3%83%83%E3%83%88-%E6%9C%80%E6%96%B0-2015-%E6%96%B9%E6%B3%95/"
"Title   : 2016年ダイエットを決意したアナタが必ず結果を出す3つの最新方法 ...キャッシュ類似ページ"
"Snipped : ハリウッド女優が実践する5対2ダイエットとは? 『5対2』って何だと思います? 5+2=7なので勘の良い 人は気付いたかもしれませんが、 1週間(7日間)を5日と2日に分けるダイエット手法なのです。 実はコレ、 2012年(3年前に)ロンドンで大流行したダイエット法 ..."
"------------------------------"
"URL     : http://www.womaninsight.jp/archives/202545"
"Title   : 2016年春夏はコレで痩せる!流行ダイエットベスト3を発表 - Woman ...キャッシュ"
"Snipped : 2016年4月4日 ... 今年こそダイエットするぞと意気込んでいる人も、年中ダイエッターの人も、必見! 「クックパッド ダイエットラボ」から、『2016年春夏のダイエット流行予測』が発表になりました。さて、今年はどんなダイエットが人気になるのでしょうか。さっそく ..."
"------------------------------"
"URL     : https://sportsclub.nifty.com/ft_diet/ranking/"
"Title   : 人気ダイエットランキング | @niftyスポーツクラブキャッシュ"
"Snipped : 2016年9月1日 ... いま注目のダイエットや隠れた話題のダイエットを、口コミ件数や評価をもとにランキ ングしてご紹介しています。人気のダイエットをお見逃しなく!"

実際にgoogle先生で検索した結果と全く同じものが取得できました。
これをAPI形式にするか、gemにしても良さそうですね〜

注意事項

本プログラムを使って、スクリプトなどに組み込んで自動的にアクセスを行うことは推奨しておりません! 理由は簡潔言うと一定期間の間にgoogle先生に多量アクセスを行うと、googleへのアタックとみなされ、ブロックされますので、多用は禁物です。
今回は技術的な検証として記事をあげたため、SEO対策などの活用目的としては推奨しておりません。

Rails5 + nginx + pumaの起動問題

Rails5 + nginx + puma でアプリケーションを起動した際に、なぜか[502 Bad Gateway] になった(nginx から puma へ繋がらなかった)時のお話です。

環境

Ruby Version 2.3.3
・Rails5
・nginx/1.10.2
・Puma Version 3.7.0

nginxの設定

特段特別な設定はしておらず、見たまんまです。

server {  
  listen 80;  
  location / {  
    proxy_set_header X-Real-IP $remote_addr;    
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  
    proxy_set_header Host $http_host;  
    proxy_redirect off;                              
    proxy_pass http://127.0.0.1:3000;   
  }  
}  
  
server {   
  listen 443;   
    ssl on;   
    ssl_certificate      server.crt;   
    ssl_certificate_key  server.key;  
    location / {    
      proxy_set_header Host $http_host;   
      proxy_set_header X-Real-IP $remote_addr;    
      proxy_set_header X-Forwarded-Proto https;    
      proxy_set_header X-Forwarded-Host $host;  
      proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;  
      proxy_redirect off;  
      proxy_pass http://127.0.0.1:3000;  
    }  
  }

起動と事象

とりあえず Rails serverの起動をしてみる

$ rails s

f:id:kno75:20170206165445p:plain

こちらは特に問題なく起動するが、localhost へアクセスしてみると 502 Bad Gatewayになる….?

f:id:kno75:20170206165739p:plain

アクセスした際のログを見てみると railsのログには何も表示されず、nginx のログにはerrorが出ている。。。

nginxのログ

2017/02/06 17:00:53 [error] 23763#0: *282 kevent() reported that connect() failed (61: Connection refused) while connecting to upstream, client: 127.0.0.1, server: , request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:3000/", host: "localhost"

そんなわけで、nginx → puma がダメっぽいことが判明!

そこからは色々とはまり、、、、、

結局の原因

結論は puma が port 3000 を IPv6でLISTENしていたことが原因でした。
rails server を起動させ port 3000 を確認したところ、なんとIPv6になっているではないですか….orz

$ lsof -n -P -i | grep 3000
ruby      26568 xxxxxxxx   16u  IPv6 0xdddee1ecd054f49f      0t0  TCP *:3000 (LISTEN)

これは気づかなかった、、、一体誰がIPv6になっていると予測ができるだろうか…
この解決としては、 rails server 起動時に以下のコマンドで指定解決します。

$ rails s -b 0.0.0.0      

再度、port 3000 の確認をしてみると

$ lsof -n -P -i | grep 3000
ruby      26568 xxxxxxxx   16u  IPv4 0xdddee1ecd054f49f      0t0  TCP *:3000 (LISTEN)

無事、IPv4になっている!

これでlocalhostに無事、アクセスできることを確認して完了です!!

rmagick の 失敗原因と解決方法をまとめてみた

Railsの新環境を整えている際に発生した rmagick 入らない問題について結構はまったので、調べた内容をまとめました。どこかで誰かの役に経てれば嬉しいです。

また、rmagick は調べてみると沢山の人が様々な原因で install 失敗しているみたいなので、記事の下の方に失敗原因別に対象方法をまとめました。


ちなみに、私が install 失敗していたときの特徴ですが

  • ImageMagick 7. x 系である
  • [ checking for wand/MagickWand.b ]と表示された
  • wand/MagickWand.h file not found (ファイルは存在するのに...)
  • 画像のようなエラーが表示

f:id:kno75:20170121201744p:plain

でした。
https://images-fe.ssl-images-amazon.com/images/I/51wL43bSolL._SL75_.jpg

失敗の原因

とりあえずログから失敗原因をみてみると、 [ MagickWand.h file not found ] となっている。
しかし、MagickWand.h を find で検索してみると、ちゃんと入っている....

そこで再度エラーを見直してみると、 #include <wand/MagickWand.h>となっている。....ん?
自分の環境をみてみると MagickWand/MagickWand.h になってる...だと....


どうやらImageMagickさんのディレクトリ名が変わっている疑惑がでてきた。
そこで ImageMagick のGit のリポジトリを見に行くと
github.com

やっぱり変わってる!?なんという罠...orz

現時点(2017年1月) ではまだ rmagick さんが ImageMagick 7系のディレクトリ構成変更に対応していないっぽい。

解決方法

そんなわけで、ImageMagick 7系だと rmagick が入らないため、ImageMagickの6系を入れなくてはいけない。

手順1 : まずは uninstall
今入っている ImageMagick 7系のuninstall
(複数バージョンを入れていると上手く動かないらしい)

$ brew uninstall imagemagick


手順2 : ImageMagick の6系をinstall
最新のMac 環境だと古いバージョンの brew install は色々と面倒です(brew verionsはもう使えない)。
ですが、入れたい過去のバージョンの git の hash がわかっていれば、入れることが可能なのです!
参考までに、以下のコマンドで ImageMagick 6系が入ります。

$ brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/6f014f2b7f1f9e618fd5c0ae9c93befea671f8be/Formula/imagemagick.rb

※ このrmagick が入らない問題は、すでにIssueに上がってましたね
Now RMagick 2.15.4 can't be built with ImageMagick 7.0.x · Issue #256 · rmagick/rmagick · GitHub


手順3 : rmagick の install
最後に rmagick を install して終了です!

$ gem install rmagick
以上で完了になります
※ もしかしたら pkg-config も再インストールしてあげるとよいかもしれません。
 (筆者はしていませんが)



その他 rmagick install で失敗する原因と解決方法

今回の原因以外の rmagick 失敗原因と・解決方法を以下にご紹介です。



原因1 : ImageMagick が入っていない人
この場合は [ imagemagick ] が純粋に入っていないだけなので、そんな人はまず imagemagick を入れてあげる

$ brew install imagemagick
更に、[ pkg-config ] も必要なため入れてあげる。
※ 再インストールして上手くいった人もいるらしいです
$ brew install pkg-config
最後に rmagick を入れて完了です
$ gem install rmagick




原因2:PKG_CONFIG_PATHの未設定問題
こちらは ImageMagick は入っているが、以下のようなエラーメッセージが表示される場合
Package MagickWand was not found in the pkg-config search path.
Perhaps you should add the directory containing `MagickWand.pc'
to the PKG_CONFIG_PATH environment variable
No package 'MagickWand' found
https://images-fe.ssl-images-amazon.com/images/I/41CLQD882zL._SL75_.jpg


これはエラーメッセージの通り MagickWand.pc にパスが通っていないためである。
そのためまずは MagickWand.pc を探す

$ updatedb
$ locate MagickWand.pc
/usr/local/ImageMagick/lib/pkgconfig/MagickWand.pc
/usr/local/src/ImageMagick-6.7.5-6/wand/MagickWand.pc
みつけたらパスを通してあげて、install
$ PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
$ bundle install



その他にも失敗原因とか見つけたらあげておきます

gem install rails 失敗 ....

本格的にRubyの勉強を始めることにし、とりあえずMacRails環境を作ってWebアプリでも作ろうかと考えました!

そんなわけでまずは環境づくりをしたわけですが、今回はつまづいた箇所のお話を〜
https://images-fe.ssl-images-amazon.com/images/I/51GLBK7MuVL._SL75_.jpg

とりあえず rails を入れてみる

何も考えず、調べた通りにrailsをインストールしてみる

$ sudo gem install rails       
結果:
ERROR:  Could not find a valid gem 'rails' (>= 0), here is why:
          Unable to download data from https://rubygems.org/ - Errno::ETIMEDOUT: Operation timed out - connect(2) (https://rubygems.org/latest_specs.4.8.gz)
ふむ、、、、失敗していることだけはわかりますね。
しょうがなくエラーを読んでみると、TIMEOUTという文字だけかろうじて理解できる。。。
原因がさっぱりだ \(^o^)/

原因はSSL系?

早速Google先生に聞いてみるとざっくりと「SSL系の問題」だということがわかった。

どうやらAddTrustExternalCARoot-2048.pemをダウンロードすると良いということがほんわりとわかり、早速ダウンロードしようとすると....

404 not found

Σ(゚д゚; ちょ ..


だがこれは調べてみると先人がいた!先輩まじ感謝
swiftfe0.hatenablog.com

そんなわけでRubygemのSSL Certificate update のページを参考に作業
SSL Certificate Update - RubyGems Guides

Rubygemのアップロード用ファイルの入手

↓ からgem update packageのダウンロード
https://rubygems.org/downloads/rubygems-update-2.6.7.gem

ダウンロードしたら適当な場所に置いておいてください

プログラミング言語 Ruby

プログラミング言語 Ruby


アップロードpackageの繁栄

ダウンロードしたファイルをinstallします

$ gem install --local ○○○/rubygems-update-2.6.7.gem    
結果:
1 gem installed

問題なく成功しているっぽいですね
( ○○○はダウンロードしたファイルのパスを指定してください)

RubygemのSSL Certificate update のページに従って作業を続けます

$ sudo update_rubygems --no-ri --no-rdoc
結果:
RubyGems 2.6.7 installed

=== 2.6.7 / 2016-09-26
.
.

.
.
RubyGems installed the following executables:
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/gem

これも成功しているっぽいですね


$ sudo gem uninstall rubygems-update -x
結果:
Removing update_rubygems
Successfully uninstalled rubygems-update-2.6.7
これでアップデート作業は終了っぽいです

rails install の再チャレンジ!

$ sudo gem install rails


キターー(゚∀゚)ーーー

これで私は成功しました!!
(実は細かいところでいろいろ引っかかってますが、それはまた別のお話)
https://images-fe.ssl-images-amazon.com/images/I/51XdQhacD-L._SL75_.jpg

Git Tagを利用したJenkinsからのデプロイ

こんにちは、カノです。
今回は Git で Tag 管理されたソースを Jenkins を使ってデプロイする方法についてです。
f:id:kno75:20160804213016p:plain

ん? Rsync を使わないのかって?
筆者は Rsync が嫌いです!
そのため、Rsync は使いません!!

なぜか?
Rsyncは事故が起こりやすく、一歩間違えれば大事故に繋がります。
dryrun をかければよいか?

答えはNoです!
なぜなら、Rsyncの実行前にdryrunをするルールがあるにも関わらず、事故は起こるからです。実際に事故が起こっている….
その他 Rsync を選ばない理由は多々ありますが、その理由だけで記事が埋まってしまいそうなので、そのお話は需要があれば別の機会に


そのため、今回はそもそも Rsync を使わず、Git の Tag を利用してデプロイの仕組みを考えてみる事にしました。

■ 前提条件

 ・Git でソース管理(Tag管理)をしている
 ・環境が テスト環境 と 本番環境 の2つ以上別れている
 ・デプロイするときは本番環境で手動作業は行わない
 ・Jenkins を使う

これだけ


■ イメージ

f:id:kno75:20160805102003p:plain
実際に絵にしてみると、とても簡単ですね。
では工程ごとに見ていきましょう


1. TagのPush

リリースを行う機能の開発が一通り完了し、Git へ Tag の push を行う。

こちらでは特別な作業は特に必要ないですね。
タグはバージョンなどで管理しておくと良いかもしれません。

例) Tag : v1.4.20
 ・メジャーバージョン (メイン機能の追加など、大きなバージョンアップを行う場合)
 ・マイナーバージョン (サブ機能の追加などを行う場合)
 ・リビジョン (バグの改修など、機能改善を行う場合)


2. Webhookを送る

まずはリポジトリの settings で Webhook で通知を送る( Jenkins さんへの)設定を行います。
検知するイベントでリリースを選択することで、リリース時に Jenkins でリクエストを受け取ることができます。
f:id:kno75:20160805111111p:plain

webhookの設定が完了したら、次はいよいよJenkinsさんの出番ですね!


3. Jenkins で pull してくる

ここからが Jenkins 内での作業 ( Job での動作 )になってきます。

Job の設定ですが、まずはJenkinsの新規Jobを作成し、そこへ基本的な Github の情報の設定を行います。
f:id:kno75:20160805111915p:plain

次に、ビルドの項目で「シェルの実行」を選択します
f:id:kno75:20160805112112p:plain

※ 最終的なデプロイのための Job (shell) は1個ですが、説明のため分割しております。

shell の中身 1:最新のソースを取得


# master へチェックアウトする(念のため)          
git checkout master
git fetch origin
# 最新のソースをpull
git pull origin master

こちらは何も特殊なことはしていませんね。
最新のソースを落としてきているだけです。


4. Tag から差分(リリース対象)の抽出

今回のデプロイ方法では、基本的にリリース対象となるファイルだけを判別しリリースを行います。

仕組みとしてはTag の差分を取るのですが、Git でdiff を取ると対象ファイルが「M:修正」、「A:追加」、「D:削除」のような識別が可能なため、デプロイではそれにあった動作を選択させることが可能なのです。

diff で取得できるイベントとそれに伴う動作

取得 記号 意味 shell での動作
M 修正があるファイル 強制上書き
A 新規追加ファイル 普通にリリース
D 削除 サーバ上から削除
C コピー 普通にリリース


実際に前回リリース時のTag と、最新のTagとの差分取得方法です。

shell の中身 2:差分取得


# タグの一覧の取得を行う
$LIST=(`git tag -l`); ← 全てのタグ一覧が取得される
# リリース対象のTag名の取得
$RELEASE_TAG=$LISt[${#LIST[@]} - 1]};   ← 最新のタグ(1番最後のタグ)を取得
# 前回リリースしたTag名の取得
$BEFORE_TAG=$LISt[${#LIST[@]} - 2]};   ← 前回のタグ(2番最後のタグ)を取得


# Tag の Diff を取る (種類単位での取得)
A_LIST=`git diff --name-status ${RELEASE_TAG} ${BEFORE_TAG} | grep "^[A]" | awk '{print $2}'`;
M_LIST=`git diff --name-status ${RELEASE_TAG} ${BEFORE_TAG} | grep "^[M]" | awk '{print $2}'`;
D_LIST=`git diff --name-status ${RELEASE_TAG} ${BEFORE_TAG} | grep "^[D]" | awk '{print $2}'`;


解説
A_LIST=`git diff --name-status ${RELEASE_TAG} ${BEFORE_TAG} | grep "^[A]" | awk '{print $2}'`;

簡単な shell 芸ですが、1つずつ見ていきましょう。

git diff --name-status ${RELEASE_TAG} ${BEFORE_TAG}  

こちらは Git Tag の差分を『 記号 + ファイル名(相対パス) 』で取得をしています。

grep "^[A]"                        

記号の A [新規追加] だけを抽出しています。

awk'{print $2}'                      

記号が余計なので、ファイル名だけを抽出しています。

分割して見ていくと、とても簡単ですね。


5. 差分のリリース

作業4で作成したTagの差分リストから、記号 (M:修正、A:追加、D:削除)に合わせてリリースしていきます。

※ 鉄則:デプロイは、既存サービスに影響のないものからしていきましょう!
  新規追加分、修正分、削除の順番に実行していくのが良いと思われます。

shell の中身 3:デプロイ


# リリース対象のベースパスの設定
$DEPLOY_BASE_DIR="/home/www/test"; ← DEPLOY_BASE_DIR は .git が置かれているディレクト
$DEPLOY_HOST="192.xx.xx.xx";    ← DEPLOY_HOST は本番環境のHost

# 新規追加分のリリース
for a_file in ${A_LIST[@]}; do
 SOURCE_PATH=${a_file};

 TARGET_PATH=${DEPLOY_BASE_DIR}${a_file};
 TARGET_DIR=`dirname ${TARGET_PATH}`;

 # ファイルのリリース(パーミッションは適切なものへ変えてください)
 scp ${SOURCE_PATH} ${DEPLOY_HOST}:${TARGET_PATH};chmod 755 ${TARGET_DIR}";
done


# 修正分のリリース
for m_file in ${M_LIST[@]}; do
 SOURCE_PATH=${m_file};

 TARGET_PATH=${DEPLOY_BASE_DIR}${m_file};
 TARGET_DIR=`dirname${TARGET_PATH}`;

 # ファイルのリリース
 scp ${SOURCE_PATH} ${DEPLOY_HOST}:${TARGET_PATH};
done


# 削除処理
for d_file in ${D_LIST[@]}; do
 TARGET_PATH=${DEPLOY_BASE_DIR}${d_file};
 TARGET_DIR=`dirname ${TARGET_PATH}`;

 ssh ${DEPLOY_HOST} "rm ${TARGET_PATH}";
done

ご覧の通り、 A:新規追加、M:修正、D:削除 などで処理を変えております。
また、リリースを行うファイル名などを管理しているため設定ファイル、画像ファイルなどの判別が可能となっており、ファイルによってのデプロイ順番なども管理できます(もちろんその分スクリプトを書きますが)。

更に、この Tag を活用したデプロイと逆の事を行えば、リストアも簡単に実装が可能です。

このように、デプロイの手法などを自分の環境に好きにカスタマイズできるので、皆さんも自分の環境にあったデプロイを実現してみてください。

Slackにポケモン絵文字をまとめて入れてみた

例に漏れずポケモンGoをやっておりますが、社内でもポケモンGoの話題が絶えません。

あまりにハマりすぎてSlackでもよく話題に上がります....

そんなこんなで Slackにポケモン絵文字をまとめて入れたので、その方法です。
https://images-fe.ssl-images-amazon.com/images/I/51SYfM4adrL._SL75_.jpg

準備1 : 画像を集める

少し調べてみるとポケモンの絵文字を提供している素晴らしい方がいた!

続きを読む