くりにっき

フルスタックキュアエンジニアです

golangのバイナリを雑にクロスコンパイルしたかった

zatsu_monitorを作った時の副産物です

sue445.hatenablog.com

やりたかったこと

  • golangのクロスコンパイルを楽にやりたかった
  • ビルドしたバイナリはzipで圧縮しつつ、zipのファイル名にosやarchもつけたかった
    • 例)zatsu_monitor_unix_amd64.zip
    • バージョン名もつくとベスト
    • zipをそのままリポジトリreleases にアップロードしたかった
  • ただしバイナリにはosやarchは つけたくなかった
    • 例)zatsu_monitor
    • 好みの問題だと思うのですが、バイナリにosなどがつくとプロビジョニングツールで削るのが面倒なので
      • プロビジョニングツールではunzipして出てきたファイルをポン置きするだけにしたかった

gox が一番理想に近かったのですが、バイナリにosやarchがつくのが嫌だったので自分でビルドスクリプトを書きました

ビルドスクリプト

https://github.com/sue445/zatsu_monitor/blob/0.2.0/build.sh

できること

  • 各OS, archごとに<バイナリ名>_<バージョン>_<os>_<arch>.zipの形式のzipファイルを作成
  • zipを解凍したらosやarchはつかないバイナリが出てくる
  • バージョンでtagを生成してpushする

使い方

Macの場合は gnu-sed をインストール

Macでデフォルトで入ってるBSD版のsedだと正規表現が使えなくて機能が貧弱なので gru-sedをインストールしてください

brew install gnu-sed

version.go にバージョン情報を記載

package main

const VERSION = "0.1.0"

build.shを適宜変更

readonly DIST_DIR="dist"
readonly BIN_NAME="zatsu_monitor"
  • DIST_DIR : バイナリの出力先
  • BIN_NAME : バイナリ名

実行

distディレクトリに出力するので予め作っておいてください。(あと .gitignore への登録も)

$ ./build.sh
Write: dist/zatsu_monitor_0.1.0_darwin_amd64.zip
Write: dist/zatsu_monitor_0.1.0_linux_amd64.zip
Write: dist/zatsu_monitor_0.1.0_linux_arm.zip

zipの中身

$ unzip -l dist/zatsu_monitor_0.1.0_linux_amd64.zip
Archive:  dist/zatsu_monitor_0.1.0_linux_amd64.zip
  Length     Date   Time    Name
 --------    ----   ----    ----
 11623512  07-06-16 23:23   zatsu_monitor
 --------                   -------
 11623512                   1 file

./build sh release のように実行すると自動的にバージョンのtagを生成してリポジトリにpushまでします

50行くらいのスクリプトなので詳しくはソースを読んでください

ライセンスとか

zatsu_monitor同様MITなので煮るなり焼くなりしてください( ◜◡◝ )

【今月のgem】kiriban_getterというキリ番チェックをするためのgemを作った

社内LT大会ネタで作ったやつ(第2弾)

モチベーション

キリ番には夢がある(断言)

github.com

使い方

refinementsを使っているので

using KiribanGetter

したところでのみ下記のメソッドが使えるようになります

kiriban?

100.kiriban?
#=> true

101.kiriban?
#=> false

111.kiriban?
#=> true

zorome?

111.zorome?
#=> true

2222.zorome?
#=> true

2223.zorome?
#=> false

kuraiban?

100.kuraiban?
#=> true

101.kuraiban?
#=> false

111.kuraiban?
#=> false

余談

  • 「最上位以外の数字が全部0」という数字の名前を調べるのが一番難しかった(いろいろググったら「位番」がしっくり来たので採用)

その他

  • kuraiban? , zorome? ともに何種類か作ってベンチマークとって速いやつを採用
    • Stringや正規表現での判定は整数演算の1.5〜2倍くらい遅いという学び
    • ただしString#lengthはチョッパヤ
  • Rubyでアクセスカウンターを作る時にご利用ください

kiribanとの違い

最初kiribanって名前のgemを作ってpushしようとしたら下記の同名gemがあったので名前を変えてリリースしました(つらい)

secret-garden.hatenablog.com

github.com

  • kiribanはRubyのコアクラス(ObjectやString)をオープンクラスしてメソッドを直接生やしているが、kiriban_getterはrefinementsを使ってるので影響範囲は using KiribanGetter したところのみ
  • kiribanの方が対応してるキリ番は多い
  • kiribanは文字列に変換できるオブジェクトなら AAA のようなやつでもチェックできるが、kirban_getterは Integer のみチェック

ベンチマーク結果

ベンチマークをとったらkiriban_getterの方が速かったです

$ bundle exec ruby benchmark/kiriban_getter.rb
Warming up --------------------------------------
    digit_1 (legacy)    72.272k i/100ms
    digit_2 (v0.1.0)    78.053k i/100ms
Calculating -------------------------------------
    digit_1 (legacy)      1.229M (± 3.0%) i/s -      6.215M in   5.060381s
    digit_2 (v0.1.0)      1.315M (± 4.3%) i/s -      6.635M in   5.057391s

Comparison:
    digit_2 (v0.1.0):  1314506.8 i/s
    digit_1 (legacy):  1229392.4 i/s - same-ish: difference falls within error

Warming up --------------------------------------
kuraiban_1? (legacy)    42.554k i/100ms
kuraiban_2? (v0.1.0)    65.051k i/100ms
zeroban? (kiriban gem)
                         9.175k i/100ms
Calculating -------------------------------------
kuraiban_1? (legacy)    598.862k (± 4.7%) i/s -      3.021M in   5.058023s
kuraiban_2? (v0.1.0)      1.010M (± 2.9%) i/s -      5.074M in   5.027767s
zeroban? (kiriban gem)
                        100.042k (± 5.0%) i/s -    504.625k in   5.057287s

Comparison:
kuraiban_2? (v0.1.0):  1010089.6 i/s
kuraiban_1? (legacy):   598862.0 i/s - 1.69x slower
zeroban? (kiriban gem):   100042.3 i/s - 10.10x slower

Warming up --------------------------------------
  zorome_1? (legacy)    10.264k i/100ms
  zorome_2? (v0.1.0)    25.113k i/100ms
zoroban? (kiriban gem)
                         7.585k i/100ms
Calculating -------------------------------------
  zorome_1? (legacy)    122.994k (± 4.9%) i/s -    615.840k in   5.019815s
  zorome_2? (v0.1.0)    329.715k (± 4.7%) i/s -      1.657M in   5.039460s
zoroban? (kiriban gem)
                         90.235k (± 3.8%) i/s -    455.100k in   5.050971s

Comparison:
  zorome_2? (v0.1.0):   329715.0 i/s
  zorome_1? (legacy):   122994.0 i/s - 2.68x slower
zoroban? (kiriban gem):    90235.3 i/s - 3.65x slower

教訓

  • gemを作る時は https://rubygems.org/ で事前にチェックしよう
  • 空いてたら名前を取られないようにbetaでもいいからリリースすべき

zatsu_monitorという雑な監視ツールを作った

社内LT大会ネタで作ったやつ(第1弾)

モチベーション

  • 社内外で公開してる個人アプリをURL監視したかった
    • 社内だとOpenStack、社外だとHerokuに計10個くらい?
    • HerokuにもRollBar *1 はあるんだけど、たまにDyno(インスタンス)の起動でこけるのは検知できない *2
  • Mackerelだと社内ツール*3 が監視できない

無いなら作ろう

github.com

使い方

雑にyamlを書いて

# zatsu_monitor.yml
google:
  type: slack
  check_url: "https://www.google.com/"
  api_token: "AAAAAAAA"
  channel: "#general"
  user_name: "zatsu_monitor"
github:
  type: chatwork
  check_url: "https://github.com/"
  api_token: "AAAAAAAA"
  room_id: "111111"

雑に実行

/path/to/zatsu_monitor -config /path/to/zatsu_monitor.yml -data /path/to/data_dir
  • -config : 設定ファイルの場所
  • -data : 直前のhttpステータスを保存するキャッシュディレクト

ステータスが変わった時だけ投稿

f:id:sue445:20160625204655p:plain

  • ホストが解決できなかった時とかは便宜上ステータス0です

仕様

yamlなので値を継承できるのが嬉しい

トークンなどを1ヶ所に定義して、監視したいサイトで継承することができます

slack: &common
  type: slack
  channel: "#general"
  api_token: "xoxp-0000000000-0000000000-0000000000-000000"

github:
  # inherit common values
  <<: *common

  # override common values
  check_url: "https://github.com/"
  chennel: "#github"

google:
  <<: *common
  check_url: "https://www.google.com/"
  chennel: "#google"

*1:アプリ内のエラーを監視するプラグイン https://rollbar.com/

*2:ログ見ても「app crashed」しか出てないので原因不明

*3:社内からしかアクセスできないアプリ

GitHubでLICENSEや.gitignoreを後から手軽に追加する方法

tl;dr

GitHub上からファイル作成でOK

前置き

GitHubリポジトリを作った直後はREADMEやLICENCEを作成するリンクが出ていて、いい感じにテンプレートを選択することができます

f:id:sue445:20160623233524p:plain

f:id:sue445:20160623233753p:plain

ただ、一度ファイルをリポジトリにコミットしてしまうとこのリンクが出なくなってしまいます

f:id:sue445:20160623234133p:plain

LICENSEを最初に作り忘れて後から追加したいってことが自分の場合よくあるので、お手軽に作る方法を偶然発見したので書きます

やり方

トップで Create new file をクリックした後に

f:id:sue445:20160623234410p:plain

ファイル名を入力するだけです。LICENSEや .gitignore ってファイル名を入力した瞬間テンプレート選択のプルダウン出てきます

f:id:sue445:20160623235336g:plain

f:id:sue445:20160623235116g:plain

てっきり今まで新しくリポジトリ作った直後にしかテンプレートから作成できないと思ってたよ、、、

僕がよく使っているalias

はてなブックマークで気になったニュース

を読んで僕がよく使ってるaliasを調べてみました

~/.bash_historyからよく使われるコマンドを集計

$ cat ~/.bash_history | grep -e "^[^#]" | awk '{ print $1 }' | sort | uniq -c | sort -r
208 g
99 t
41 be
29 cd
26 wget
14 gs
13 ag

多い順に出しました。

grep -e "^[^#]" で先頭の # を除外してるのは HISTTIMEFORMAT でコマンドの実行時刻を記録しているためです ref. http://qiita.com/kawaz/items/92457e3d1664383b18bc

  • g
    • git
  • t
    • tig
  • be
    • bundle exec
  • gs
    • git status -sb

https://github.com/sue445/dotfiles

gコマンドのみで集計

gが圧倒的に多かったのでサブコマンドでも集計しました

$ cat ~/.bash_history | grep -e "^g " | awk '{ print $1, $2 }' | sort | uniq -c | sort -r
42 g n
41 g co
28 g push
17 g ci
17 g br
15 g di
10 g fm
7 g ca
6 g sy
  • g n
    • git now --all --stat
    • tmpコミットを作る時によく使います。コミットメッセージは下記のようになります
commit 3ead7875c942d057f6918807210305b717f61403
Author: sue445 <sue445@example.com>
Date: Thu Jun 16 15:18:26 2016 +0900
[from now] 2016/06/16 15:18:26
app/controllers/application_controller.rb | 3 +++
1 file changed, 3 insertions(+)

追伸

このエントリは はてなブックマーク & はてなブログをアプリで使いこなして、はてなTシャツをもらおう! - はてなブックマーク開発ブログ の応募レギュレーションの関係でコマンド実行以外は全部iPhoneで書いています。フリックでmarkdownはきつい、、、

Jenkinsを安全にアップデートする方法

手持ちのJenkinsをいくつかアップデートすることがあったので備忘がてらまとめておきます。*1

Jenkins 1系 -> 2系などの大幅アップデートに限らず、プラグインのアップデートでも使えると思います。

事前にやるべきこと

アップデート後に何か問題があって戻さざるを得ないこともありうるので、すぐに元に戻せる状態にしておきます。*2

Jenkins本体のバックアップ

jenkins.warをそのまま使ってる場合

jenkins.warをどこかにバックアップするか、アップデート前のバージョンをどこかにメモっておいてください。

jenkins.warの過去バージョンは http://mirrors.jenkins-ci.org/war/ からダウンロードできます。

yumやaptを使ってる場合

yum installapt-get install では最新版しかインストールされないので、ロールバックも考慮するとパッケージ指定でインストールした方がいいと思います。

rpmdebはこちらからダウンロードできます

ちなみにパッケージからインストールしておくとjenkinsユーザ作ってくれたりデーモン化までやってくれるのでおすすめです

自分の場合こういうitamaeレシピ書いてます(Debian版)

jenkins.rb

execute "wget http://pkg.jenkins-ci.org/debian/binary/jenkins_#{node[:jenkins][:version]}_all.deb -P #{node[:jenkins][:deb_dir]} && dpkg -i #{node[:jenkins][:deb_dir]}/jenkins_#{node[:jenkins][:version]}_all.deb" do
  not_if "ls #{node[:jenkins][:deb_dir]}/jenkins_#{node[:jenkins][:version]}_all.deb"
end

node.yml

jenkins:
  version: "2.8"
  deb_dir: "/data/backup/jenkins_deb"

コマンドに書き下すとこんな感じ

cd /path/to/backup_dir
wget http://pkg.jenkins-ci.org/debian/binary/jenkins_2.8_all.deb
dpkg -i jenkins_2.8_all.deb

この場合でも戻す場合は過去バージョンのrpmdebでインストールしなおすだけです

最初ymlでバージョン変えてitamae実行したのですが、 /etc/default/jenkinsJENKINS_HOMEJVMのメモリ割り当てで編集してた関係で差分適用するかどうかの入力待ち*3でずっと止まってたので、もしかしたら手動の方がいいかもしれないです。

プラグインや設定一式のバックアップ

手前味噌ですが jenkins-backup-script を使うのがいいです。設定ファイルやプラグイン一式全部バックアップされます。

sue445.hatenablog.com

こういう風にジョブ登録しておいて1日1回夜中に定期実行したり、Jenkins本体やプラグインのアップデート前に手動実行したりしています。

f:id:sue445:20160612141547p:plain

一応tarは外部のバックアップサーバにも転送してるので、突然Jenkinsマシンが死んでもすぐに復旧できる状態にはなってます

Jenkins本体はそうでもないのですが、プラグインは稀に下位互換性をぶっ壊すような変更が入るのでバックアップは必須かと *4

アップデート手順

warなりdebなりrpmなり最新版でインストールし直すだけ。

確認方法

主要なジョブをいくつか手動実行してみる

戻す方法

  • jenkins.warを使ってる場合は過去バージョンのを置き直すだけ
  • rpmdeb使ってる場合は過去バージョンでインストールしなおすだけ
  • jenkins-backup-scriptは下記のようなコマンドで設定ファイルを上書きするだけ
cd /path/to/backup_dir
tar xzvf backup.tar.gz
sudo cp -R jenkins-backup/* /path/to/jenkins/
sudo chown jenkins:jenkins -R /path/to/jenkins/

アップデート時にハマったこと

Branches to build で変数が取れなくなった

jenkins-gitlab-merge-request-builder-plugin を使ってた時にビルド対象のブランチ名がとれなくなったハマりました

設定

f:id:sue445:20160612195822p:plain

正常な時のログ

20:11:03  > git rev-parse refs/remotes/origin/feature/remove_alias_method_chain^{commit} # timeout=10
20:11:03  > git rev-parse refs/remotes/origin/refs/remotes/origin/feature/remove_alias_method_chain^{commit} # timeout=10
20:11:03 Checking out Revision 4e9593d92e293c5ca99eaa6de3b92b973cb1716a (refs/remotes/origin/feature/remove_alias_method_chain)
20:11:03  > git config core.sparsecheckout # timeout=10
20:11:03  > git checkout -f 4e9593d92e293c5ca99eaa6de3b92b973cb1716a

エラー時のログ

19:09:00 Gitlab Merge Request #9222 : xxxx/my_xxxx/feature/update_ruby => master
19:09:01  > git rev-parse refs/remotes/origin/${gitlabSourceBranch}^{commit} # timeout=10
19:09:01  > git rev-parse refs/remotes/origin/refs/remotes/origin/${gitlabSourceBranch}^{commit} # timeout=10
19:09:01  > git rev-parse refs/remotes/origin/${gitlabSourceBranch}^{commit} # timeout=10
19:09:01 ERROR: Couldn't find any revision to build. Verify the repository and branch configuration for this job.

GitLabのwebhookからJenkinsにブランチ名は渡されてるっぽいのだけど、 git rev-parse する時にはブランチ名がとれなくなってるような挙動。

「明示的に設定したパラメータしか変数としてバインドされなくなったのかなー」と思って「ビルドのパラメータ化」のところに gitlabSourceBranch を定義したらビルドが動くようになりました。*5

f:id:sue445:20160612200104p:plain

所感

  • 1系 -> 2系のアップデートで身構えてたけど、アップデート自体は驚くほどサクッとできました
  • パイプラインやJob DSLに全部置き換えるとかだと大変そうだけど、既存のジョブをそのまま移行する分には問題なさそうです
  • とはいえ保険が多いにこしたことはないのでバックアップはしておいた方がいいです

まとめ

  • 何かあった時のロールバック手順は必ず用意しておく(石橋を叩いて渡るくらいが丁度いい)
  • Jenkins 2系はいいぞ

ブコメレス

id:C_L

JenkinsCIををrpmでアップデートすると、$JENKINS_HOMEをchown -Rする糞コマンド入っているので、workspace/以下を綺麗にしておくかexport JENKINS_INSTALL_SKIP_CHOWN=trueしておかないと何時間もupdate終わらんとかになる

ナルホディウス。debだと時間かかっていなかったのでプラットフォームごとに違うのですね。(自分のところのJENKINS_HOME配下もファイル数カオスなので一瞬でchown -Rが終わるとも思えない)

*1:Jenkins 3つ管理してて、それぞれ1.6系から2.8へのアップデート

*2:過去にアップデートしたらダッシュボードが全部吹っ飛んで、バックアップもなかったので脳内ソースを頼りに半日がかりで復旧したことがあります(;´Д`)

*3:上書きしますか?(y/n)的なやつ

*4:http://sue445.hatenablog.com/entry/2016/01/20/232500

*5:リリースノートちゃんと追ってないのでどこで変更あったかは不明

Railsでmysql-clientとmysql-serverのバージョンが食い違ってハマった

GitLab CIでRailsアプリをお手軽CI開発する - Tech Inside Drecom で書ききれなかったおまけです。

tl;dr

あらすじ

GitLab CIでRailsアプリをお手軽CI開発する - Tech Inside Drecom を書いてる時に .gitlab-ci.yml

services:
  - mysql:5.5

って書いていたのになぜか activerecordmysql 5.7以降でしか使えないDDLを発行してmigrationが失敗するという事象が発生。

エラーの状況

migrationファイル

こんな感じのmigrationを実行したかった

class TableRenaemStatusToProfile < ActiveRecord::Migration
  def change
    rename_table :user_statuses, :user_profiles
  end
end

ローカル (MySQL 5.5)

問題なし

Migrating to TableRenaemStatusToProfile (20140126044757)
   (25.6ms)  RENAME TABLE `user_statuses` TO `user_profiles`
   (13.4ms)  CREATE UNIQUE INDEX `index_user_profiles_on_user_id`  ON `user_profiles` (`user_id`)
   (8.7ms)  DROP INDEX `index_user_statuses_on_user_id` ON `user_profiles`

リネーム対象のテーブルにあったindexも新テーブル名に即したindex名に変更された。(activerecord賢い)

GitLab CI

シンタックスエラー(;´Д`)

Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INDEX `index_user_statuses_on_user_id` TO `index_user_profiles_on_user_id`' at line 1: ALTER TABLE `user_profiles` RENAME INDEX `index_user_statuses_on_user_id` TO `index_user_profiles_on_user_id`

user_statusesuser_id というカラムにunique indexが貼られていたため rename_table 内でindex名変更のDDLが発行されていたのですが、MySQL 5.7以降でしか使えない RENAME INDEX が発行されてエラーになってる模様。いやお前5.5だろ。。。

activerecordどの〜!

原因

原因はactiverecordmysql serverのバージョンではなく、Dockerイメージで使ってるlibmysqlclientのバージョンが5.7で、activerecordはそこからMySQLのバージョンを取得していたためでした。*1

ためしにビルドスクリプトデバッグログを出したところ、activerecord上は確かに5.7でした

+ bundle exec rails r 'puts ActiveRecord::Base.connection.send(:full_version)'
5.7.12

今回使ってたDockerのイメージの中を調べたらlibmysqlclientとかのバージョンとも完全一致

$ docker run -i -t drecom/ubuntu-ruby bash
root@8c92dcb22de8:/# dpkg -l | grep mysql
ii  libmysqlclient-dev                   5.7.12-0ubuntu1                     amd64        MySQL database development files
ii  libmysqlclient20:amd64               5.7.12-0ubuntu1                     amd64        MySQL database client library
ii  libqt4-sql-mysql:amd64               4:4.8.7+dfsg-5ubuntu2               amd64        Qt 4 MySQL database driver
ii  mysql-client                         5.7.12-0ubuntu1                     all          MySQL database client (metapackage depending on the latest version)
ii  mysql-client-5.7                     5.7.12-0ubuntu1                     amd64        MySQL database client binaries
ii  mysql-client-core-5.7                5.7.12-0ubuntu1                     amd64        MySQL database core client binaries
ii  mysql-common                         5.7.12-0ubuntu1                     all          MySQL database common files, e.g. /etc/mysql/my.cnf

MySQLのバージョンによるmigrationの挙動の違い

activerecordのソースを読んだところ、MySQL 5.7以降だと RENAME INDEX を使って、MySQL 5.6以下だと CREATE INDEX してから DROP INDEX しているようでした

対処法

冒頭に書いた通りRails 5系では治ってます。Rails 4系でこの問題に直面したら config/initializers/activerecord_mysql_version_patch.rb のようなモンキーパッチを入れれば治ります

# activerecordでクライアントではなくサーバのMySQLのバージョンを使用するためのモンキーパッチ
# via. https://github.com/rails/rails/commit/977ffe880624bbd05f5ee1cc6e4fa51a999884ab

current_version = ActiveRecord.version
if Gem::Version.new("5.0.0.beta1") <= current_version
  raise "This monkeypatch's lifetime is over. Check ActiveRecord version."
end

module ActiveRecord
  module ConnectionAdapters
    class Mysql2Adapter < AbstractMysqlAdapter
      def full_version
        @full_version ||= @connection.server_info[:version]
      end
    end
  end
end

Rails 5系では治っているので、Rails 5系に上げた後にモンキーパッチが残り続けないようにわざとエラーを投げるようにしています。(いわゆる「モンキーパッチの賞味期限」というやつ)

モンキーパッチの賞味期限は下記のスライドが詳しいです

https://speakerdeck.com/eagletmt/activerecord-3-dot-2-4-dot-1?slide=26

まとめ

mysql clientとmysql serverのバージョンが食い違うことは早々ないと思いますが上記のようなやり方で回避は可能です

これはGitLab CI以外にも、Dockerコンテナ採用しているWerckerでも同じ現象が起こりそうな予感。

kamipoさんには足を向けて眠れません m(_ _)m https://github.com/rails/rails/commit/977ffe880624bbd05f5ee1cc6e4fa51a999884ab

*1:activerecord 5系だとmysql serverからバージョンを取得している