AWS ECRの東京リージョンも出たことだしShippableでそれを最大限活用してみる
Shippable記事が割と反応あったのにみんなが使ってるよって報告がなくてつらいです。
ちなみに先日教えて頂いたのですがShippableのキャラクターはアイアイだそうです。
南の島のおさるさんのアイアイです。日本人なら誰でも知ってる、みんなのうたのアイアイです。
これはもう最高にかわいいというのは疑問の余地はありませんね*1
ECR Tokyo Region
この前ECR*2の東京リージョンが来てましたね。
いままでDocker hubを使っていたのですがECRの東京リージョンはやっぱりはやいです。
イメージのpush/pullが遅いと暇をもてあまし気味なのではやいのはいいですね。
今回はShippableを使いながらECRを活用するためにやったことを紹介します。
BYON
ShippableにはBYONという機能があります。Bring Your Own Nodeの略です。
これを使うことで自前インフラでCIをまわすことができるようになります。
インフラは通常Shippableが用意してくれてるのですがこれを自前インフラに変える機能ですね。
具体的にはセキュリティ的な要件で自社インフラ以外を使えない人達向けの機能のようです。
ですが、それ以外にもメリットがあります。それはキャッシュです。
Shippableのインフラを使っている場合にはときどきキャッシュが消えることがあります。
たぶん向こうで定期的に殺してるからでしょう。自前インフラならキャッシュが消える心配は必要ありません。
具体的な設定方法は上記ページに詳しいのでここでは詳細は省きます。
ざっくり説明すると2種類の方法があります。
1つはShippableからのSSHを許可してShippableのssh-agentに実行してもらう形式です。
もう1つはスクリプトをダウンロードしてそれを対象のマシンで実行する形式です。
どちらも簡単ですし、どちらを選んでもいいと思います。ちなみに僕はスクリプトダウンロードするほうを選びました。
共通する注意点としては以下の2点です。
- 動作要件にRAM1.8GBがあるのでt2.small以上が必要
- 動作要件にSSD空き容量30GB以上があるのでボリュームの追加が必要
BYONの初期設定が終わったらあとは普通に使うだけです。
BYONの良い点
ECRの転送量がおさえられる
ECRの料金設定は主に2つあり、ストレージ容量とアウト転送量によって計算されます。インの転送量は関係ありません。
アウト転送量は同一リージョン内での転送なら無料なのでECRのアウト転送量は抑えられます。
はやい
Shippableだとキャッシュがのらないときはやはり遅かったですが、こちらはそんな心配はいりません。
CIが爆速であるというのは嬉しいです。まあ遅くて困るかっていうとそんなにはこまんないんですが……。
デバッグがしやすい
手元でOKなのにCIで落ちることってありますよね。
CIまわりやってて一番ストレスになるのがこれだと思うのですが、自前インフラなのでSSHできます。
落ちたときのイメージも残ってます。超手軽にデバッグできます。最高!
BYONの悪い点
Dockerのバージョンが古い
Docker 1.9です。
ウチではCI用自前イメージ内でdocker-composeしてテスト走らせてるのですが、version 2のdocker-compose.ymlは弾かれました。
もしかしたら走らせてるイメージの問題かもしれませんが……。
自前インフラなんだし、自分であげてもなんとかなるんちゃうのっておもって1.12をいれたら動かなくなりました。
ちなみにぶっ壊してももう一回同じセットアップスクリプトを走らせたら直ります。こういうのは便利。
インフラコストが高め
無料に比べたらt2.small以上 + SSD30GBつきになるので高くなります。
まあはやさとデバッグの楽さをお金で買ってるんだと思えば許せるレベル、かもしれません。
もしかしたらセットアップスクリプト実行のときに、スクリプトを書き換えたらそのへんの要件も突破できるかもしれません。*3
というわけでECR東京リージョン使うんだったらBYONで自前EC2インスタンスでCIまわすと便利だよ。
しかもセットアップは超カンタンだよ、って話でした。
日本語のShippableネタはまだまだ少ないのでみなさんのウチではこう使ってるよ話をお待ちしております。
一昔前のwebのテキストコンテンツ
どうやら僕は一般の人よりも多くwebサイトのテキストとか漫画を読んでいるらしいです。
自覚はないけどよくそう言われます。*1
だいたい昔読んだ面白かったコンテンツを出しても理解されないので、
今日は特に記憶に残ってるけど誰にも通じたことがないサイトを紹介しようと思います。
ロト15代
ドラクエ二次創作サイト。出会いは、百万ゴールドの男が完結したかどうかくらいのときだったから2008年くらいかな。
ゴールド使用禁止プレイのプレイ日記である『百万ゴールドの男』と『ロトの借財』がまとまってる。
そっちも好き、というかルディもね、ローラ姫もね、ヒロイン力高くてめっちゃ好き。
センドの心理描写も好き。
プレイ日記もいいんだけど、 http://esupa.xrea.jp/nicky/nicky.cgi こっちにある1ページ漫画も好き。
びっくりするくらいのドラクエ愛にあふれてておもしろい。
もっと読みたいと思ってひたすら過去ログ漁りながら、当時はnicky改造日記とか全然わかんないまま読んでたものです。
プログラマーになった今ならわかるかなとおもって開いたらPerlだったので一瞬で閉じました。
Perlだったと一目で認識できるくらいには成長したんだなと思っておきます。
個人的に好きな妄想設定はタニアとグプタです。
- http://esupa.xrea.jp/nicky/nicky.cgi?DTO=20060912B
- http://esupa.xrea.jp/nicky/nicky.cgi?DTO=20090125A
あとロト15代以外にいわゆるテキストサイトっぽいものもあってそれはたぶん3周くらいしました。
子供の頃の夢について語り出すのが好き。あとファイナルもときどき思いだす。
さすがに何番目くらいにあったかは覚えてないのでみんなも全部読めばいいと思います。
フルフラット
http://www.geocities.co.jp/Bookend-Ryunosuke/6206/index.html
web小説ってやつ。たぶん2002年くらいに出会った、気がする。
オススメは代表作の『Fantasia』。
第一部完のあと第二部がぜんぜん続いてませんけど面白いです。
アーヴィスも好きなんだけどヒリュウくんが好き。
戦略バトルもののワクワク感に初めて触れたのはこの作品だったと思う。それまではドラゴンボールとかだったから。
ここの管理人さんが日記で『ひぐらしのなく頃に』と『撲殺天使ドクロちゃん』と『わたしたちの田村くん』に言及していて
そこからラノベとかに手をだすようになりました。
そういう意味での影響が大きく思い出深い感じがあります*2。
終局世界物語
http://worldendstory.blog.fc2.com/blog-entry-116.html
web小説で、えーと、いわゆる終末モノ?
そういうジャンルがあるのかよくわかってないけどまあそういうお話です。これも中学生くらいに読んだ気がする。
拓さんかっこいい。描写的には全然違うんだけど同時期にちょっとだけ読んでた『DAN DOH!!』の赤野拓也の顔が思いだされます。*3
終末っぽく世界がいろんな感じに崩壊していくのがたまらなく、つらいような、ステキなような、悲しいような感じです。
好きなエピソードは自衛隊の人たちが抗議するシーンと、先生のやつ。*4
完全世界物語ってのもあったんだけど、消えちゃったのかなあ。
サイトが消えたあとにmixiでひっそり活動してたのは見つけたんだけど。
《さまよえる蒼い弾丸》のかっこよさったらなかった。
作者の神楽坂司さんは映画感想も書いていて、僕の中で映画いっぱい見る = かっこいいのイメージが崩れないのはこの人のおかげです。
10年以上も前だったと思いますがカイジの映画化の嘘感想を読んだ覚えがあります。
最後映画の新キャラ(米倉涼子)がストッキングにいれたメロン振り回して鉄骨の上からカイジ(哀川翔)を落とそうとするってシーンの感想はなかなかの迫力でした。*5
そういえば完全世界物語のイラストを『無敵看板娘』の佐渡川準さんが描いてた気がします。
あといろいろ漫画とか書こうと思ってたんですけどめんどくさくなったので終わります。
過去にハマった好きなものというのはその後の自分を構成する要素なので、
それらについての「いまの気持ち」をダンプしておくことは意味があることなのかなとやってから思いました。
気が向いたらまたやります。
Crystal書いてて気づいたこと
今日はuser agent parserのruby版をcrystalに移植していた。
crystalはv0.18.7を使っているのだけど書いてて気づいたことがあるのでメモ
not nilの推論はインスタンス変数には効かない
crystalは型に厳しいです。変数fooがStringあるいはNil型であるときはどちらの型にも存在するメソッドしか呼べません。
Stringにしかないメソッドを呼ぶときには条件分岐でnot nilであることを保証させます。
# foo : String?とする if foo.nil? # ここではfooはNil型 else # ここではfooはnot nilなのでString型 end
ローカル変数ならこれでおkですがインスタンス変数はこれだとダメです。
class Klass @foo : String? def size if @foo.nil? 0 else @foo.size end end end puts Klass.new.size # Error in line 13: instantiating 'Klass#size()' # # in line 8: undefined method 'size' for Nil (compile-time type is (String | Nil))
いちどローカル変数にとれば通るみたいです。インスタンス変数だと関数スコープ外から変更可能だからかな、って推測しています。
class Klass @foo : String? def size bar = @foo if bar.nil? 0 else bar.size end end end puts Klass.new.size
#to_sと#inspectのoverride
Object#to_s
と Object#inspect
はoverrideするべきではないらしい(usually MUST NOT)
ref: https://crystal-lang.org/api/0.18.7/Object.html#to_s-instance-method
代わりに #to_s(io : IO)
と #inspect(io : IO)
に対してやれとのこと。
def Klass def to_s(io : IO) "this is Klass".to_s(io) end end puts Klass.new.to_s #=> "this is Klass" puts "#{Klass.new}" #=> "this is Klass"
*argsはTuple型
調べればわかるんだけど調べたのでメモ。型違うのでちょっと注意。
def foo(*args) puts args.class end foo(1, 2, 3) #=> Tuple(Int32, Int32, Int32) foo("bar", "foobar") #=> Tuple(String, String)
オフラインリアルタイムどう書くE06 Ruby で解く
問題はこちら:
コードは末尾に。
感想
第一感
ルールがしっかりしてるので適当にclass作って適当に殴ってればなんとかなりそうだなって印象。
テストケースの分量みてもナイーブな実装で特に問題なさげ。
というわけでホゲモンとトレーナーを作ってバトらせればいいかなって感じ。
実装
実際にその通りで問題なかった。 combination / each_sliceあたりがやっぱ便利だった
バグらせたのは2点。
- コピペミスで対戦相手の勝利点が1じゃなくて2増えてしまった
- sortするときにbreakするのを忘れた
心残り
- テストコードが雑
- 光り輝くグローバル変数
- RGBのあたりはmod3とかで回すと簡単になる気がした
- battle_withが引数を変更するのでなかなかびっくりさせられる
- battleをさせる別クラスが必要だった
- ソートがもうちょっとエレガントにできるみたい
- 勝数をマイナスにする
- 勝数 * 100 - idとかにするとか
コード
require 'tapp' class Monster attr_accessor :level attr_accessor :type attr_accessor :hp def initialize(l, t) @level = l.to_i @type = t @hp = @level end def recover @hp = @level end def dead? @hp <= 0 end def battle_with(other) until dead? || other.dead? damage = damage_with(other) if level == other.level other.hp -= damage.first @hp -= damage.last elsif level > other.level other.hp -= damage.first break if other.dead? @hp -= damage.last else @hp -= damage.last break if dead? other.hp -= damage.first end end end def damage_with(other) return [2, 2] if type == other.type case type when "R" other.type == "G" ? [4, 1] : [1, 4] when "G" other.type == "B" ? [4, 1] : [1, 4] when "B" other.type == "R" ? [4, 1] : [1, 4] end end def to_s "#{hp}/#{level} #{type}" end end class Player attr_accessor :id attr_accessor :monsters attr_accessor :count def initialize(id, str) @id = id @count = 0 @monsters = str.split('').each_slice(2).map { |a, b| Monster.new(a, b) } end def to_s puts "player#{id} #{count}win" @monsters.map(&:to_s).join("\n") end def defeated? @monsters.all?(&:dead?) end def head_monster @monsters.reject(&:dead?).first end def battle_with(other) monsters.each(&:recover) other.monsters.each(&:recover) until defeated? || other.defeated? head_monster.battle_with(other.head_monster) end # ひきわけ # raise "これどうすんの" if defeated? && other.defeated? return if defeated? && other.defeated? if defeated? other.count += 1 else @count += 1 end end end class Solver def solve(input) parse(input) @players.combination(2) { |a, b| a.battle_with(b) } sorted = @players.sort do |a, b| (b.count <=> a.count).tap { |e| break a.id <=> b.id if e == 0 } end sorted.map(&:id).join(",") end def parse(input) @players = input.split(',').each.with_index(1).map { |e, i| Player.new(i, e) } end end $no = 0 $fail = 0 def test(input, expected) $no += 1 actual = Solver.new.solve(input) if actual == expected puts "#{$no} ok" else $fail += 1 puts "#{$no} ng" puts "input : #{input}" puts "actaul : #{actual}" puts "expected: #{expected}" end end test("9B,3R2G,1R2B3G", "1,3,2") test("1G", "1") test("1G,1R,1B", "1,2,3") test("8B,3R2G,1R2B3G", "3,1,2") test("6G,9R7B7B", "2,1") test("5B1B,1G1B2R6G,7B6G4B6B", "3,2,1") test("7R,2R9G,6R4B1G6R,5G1G6G", "3,1,2,4") test("2B9G8B3R,4R3G,2B,8B", "1,2,4,3") test("1B,5G1R1B4R,5R,8B9B4G,7G5R8G", "5,4,3,2,1") test("9G5B,6B6R1R5G,7G6G,8B5R,5G7G,2G5B7B", "2,1,3,4,5,6") test("5B,1B8R2B,8G6R4B,4B1G6R8G,3B6G6G5R,7B", "3,4,5,6,1,2") test("2G2G7G9B,6G5G5R,2G,4G,5R,3G8R,6G9R", "1,7,6,2,5,4,3") test("6G7B4R6B,9R4G,6G5B5G3B,6R7G,9B,7G7B8R,5G8G2R,6B8G7B1B", "8,1,6,2,3,4,7,5") test("9B8B2G4B,2B1R,7R6G8R,4R,1G7B7R,4B3B4R,4B3R2R4G,4G9R9R", "1,3,8,5,6,7,2,4") test("5G,3G,9G7G8B,7B,8G6B1B5G,1G3B,5G8R,6G,7B", "3,5,7,8,1,2,4,9,6") test("5B1R5B,6R,7R7R,8B1B,6R1G,7B3R2R,4R3B,6G1R8G,6B4R4R2B,9G5B", "10,1,4,6,9,3,8,5,7,2") test("7R4G1G6R,9B3G3R4G,2G7G,5B,5R8R,9G7R9B,8R7R5G,7B9R1R8R,7R,9R1B", "6,8,7,2,1,5,4,10,3,9") test("3G8B2B8G,7B7R5G,4B9G2R,4G,1G2R5R8R,1B,8R9G7G,7R6B,6B8B,3G3R,3R2R", "2,7,1,3,9,5,8,10,11,4,6") test("5G3B,4B3G,7G8R2B7R,6G,1G,1B,1R9R2R7R,3R4G1R,4B3G2G8G,3B,2B1G,7R", "3,7,9,8,1,4,2,12,10,11,5,6") test("4B2B5G1G,2G2B3R,7G4B9R9G,7R9G,5B,5G3G,7R5R,4B,6G3R4G3G,3R9G,8R9G4R,2R", "3,11,4,7,10,9,1,5,6,2,8,12") test("8R,9R,5R,4G,3G2G1R,5G,4G5G,2G,6G6B1G,8R2G6B2G,1G5B8B,1G,7R", "10,9,2,1,11,13,3,7,6,4,5,8,12") test("2R,4G7G,4R,1G1R7G,5B6G,2G,4B9R,7R2B7R4B,3B1G5G,8B9R,6B1G6R,1R2R,9G2B2R,4R9B", "10,11,14,8,13,5,7,2,3,4,9,1,12,6") test("6R3R3B,1R,7R4B4G,7G9B,4G6G8B,4R7R4B,5R3R,3R,5B2G4R,1B,5B,9B2R,5G4R,6R3R3G", "4,3,5,12,9,14,6,11,1,13,7,8,2,10") test("5B7B,8G,7G,6R9B3B,2B,3G3B8R7B,7R7G6R,4B6B5G,4R4G9R,4B7G6G5G,3B8B1B1G,5G7G2R,1B,2G6G5B3G,4R4B8B", "7,6,9,10,4,12,14,2,8,15,3,1,11,5,13") test("8B9B,6R3B2G,5B,6R2R5R,3R1B,1R1R1B9B,4R4B9G9G,8R2B,6B,1B,2R,4B,6G7R,7G,3G2G7R,7B7G8G", "7,16,1,2,13,6,15,8,9,14,3,4,12,5,11,10") test("8R,4G,8G5G,7G1R1R7R,6R,2G3B5B,7G3R1B,4B9G9G,5R4G5R7B,8B9B1B4G,5G9R1R,8B,7G1B,9B3R,2R9G,6G5G", "9,11,4,8,1,3,7,10,15,5,14,16,6,13,2,12") test("1G3B8G,8R6B9B9B,7B,7R3B5G1B,3G,7B8G9B,2B2G6B6B,2B7G9R1B,2G8B6R8R,3R,9B3G5R2G,5B2R3R5B,8B4B,4G1R,2B,8R1B7B4R,9B", "6,2,9,11,8,1,4,7,16,12,17,13,14,3,5,10,15") test("2B,6G1G6G4R,3B8B3B,9G,1R,3B,7R9G2R,6R1G4G6B,3B5G8G,8G1G3B4R,4G8G,6G2B5B,4G2G5R,1B,4G6G,3G1R9G,8B5R4B7R,4R3B", "7,8,17,10,2,9,13,16,18,4,12,11,15,3,6,1,5,14") test("9G,6B3G1B4B,4B3R2R5G,2R1G,6B6R8B1R,4R3R1R,9R,8R8B,4G,3G9B,6G8B2R,5R8R6G,5B1B7B4B,2R3G1G3B,3R5B4R,8G5G,5G2B2R,8G", "5,11,12,3,8,15,10,16,1,7,2,13,6,18,14,17,4,9") test("7B9R,2B3R1R2R,2G6B3G,6R,8G,6B7B6R,1R1G5B6G,9G,2R6G,7B6B9G5R,5G4G1B7B,4B9R2B5G,2G8B9G9G,8G3B5R,3G,2R,3R2B9B,8B3B,1R", "10,13,14,6,12,1,11,17,8,7,2,3,5,9,4,18,16,15,19") test("8G6B9G,8G,7B4G2G6G,3B8R2R,4R1R3R8B,3G,2R1R,1R9G2B1G,4G8G,8B8B2R8R,2R1R1G,4B2B6R4B,6G9G3G6R,9B6B8R,9R7B,3G,5B4B,4B4G6B8B,4B5G,8B2R", "1,14,10,13,15,18,3,5,8,9,12,2,20,4,19,17,11,7,6,16") puts $fail
ひさびさRubyでtapしてたら諸行無常を感じた
ここ2, 3ヶ月はGolangとjsとCrystalばっかりさわってて最近Ruby全然書いてなかったのですが一昨日からまた書いてます。
Rubyには『メソッド内の最後の評価値が戻り値になる」という言語仕様がありますね*1
そうするとFactoryっぽいものを書いてると、まあ例えばこうなるわけです。
module HogeFactory def create(opt) h = Hoge.new h.fuga = opt[:fuga] h.piyo = poyo(opt[:piyo]) h end end
んで、この最後のhがなんとなく落ち着かなさを演出するので、例えばこんな感じにできます。 参考: [初心者向け] RubyやRailsでリファクタリングに使えそうなイディオムとか便利メソッドとか - Qiita
module HogeFactory def create(opt) Hoge.new.tap do |e| e.fuga = opt[:fuga] e.piyo = poyo(opt[:piyo]) end end end
うん、最後の戻り値のhがなくなってすっきりしましたね。
いま書いてるのってyamlをパースして各クラスにマップするところなんでこういうコードが死ぬほど出てくるんですよね。
そんでずっとdefしてnewしてtapしてendするコードを書いてて思ったんだけど、これほんとにキレイなんすかね……。
賽の河原の地獄なんじゃないかと思ったけど……
というのが昨日の感想で、今朝ブログにまとめてるときに「どっちがマシかといったら下」なんだから別にtapに問題があるわけじゃないなって思い直しました。
我々に本当に必要なのはyaml to objectなmapperなのだなあ。
*1:これは正確な表現なんだろか。「最後の式の評価値が戻り値になる」と書きかけてなんとなくいまの表現にしている
Shippableでまた落とし穴にハマった話 PR作成編
この前こういう記事書いたらそこそこ伸びたようでありがとうございます。
ウチでも使ってるよ的な話は聞けてないので、使ってる方は是非なにか書いてもらえると嬉しいです。
さて、また落とし穴にハマったので日記です。今回のは僕はびっくりしたけどそうでもない人もいるかも。
Pull Request と 環境変数$BRANCH
PR作成したときにハマった落とし穴の話です。
コンボが決まると意図しないデプロイが走ったりすると思うんでお気をつけ下さい。
Pull Request
ShippableではpushをフックしてCIが走ります。まあいわゆるCIサービスとしては普通ですね
実はそれ以外にPull Requestの作成時にもCIが走ります。
なのでPR作成直後は必ずそのPRはCIステータスが黄色(Running)になります。CIが通るまで待ちましょう
僕はCircleCIとShippableくらいしか使ったことがないんですがPR作成時にCIが走るのって一般的なんでしょうか?
環境変数$BRANCH
ShippableにおいてShippable側が用意してくれている環境変数が多数あります。
前回の記事で紹介した $SHIPPABLE_CONTAINER_NAME
や $SHIPPABLE_BUILD_DIR
などもそうです。
そういったCI環境の設定値以外に、メタなデータとしてブランチ名がはいっている $BRANCH
やビルド番号がはいっている$BUILD_NUMBER
などもあります。
topic-aブランチでCIをまわしたときは $BRANCH
は何がはいるでしょうか。当然 "topic-a"
ですよね。
では上述のPullRequest作成時を考えます。topic-aからmasterブランチにPRを作ったときは$BRANCHは何になりますか。
これは "master"
になります。
ふーん……
なんでだよ!!!
いやまあ確かにPR作成時に走るCIなので最終コミットと全く同じ状態でやってもしょうがないんですけど……。
これを踏まえて、「masterの最新の状態をlatestタグをつけてdocker pushしたい」というのはこんな感じになります。*1
条件の前半を省略するとまだmasterにマージされてないPRの段階でlatestをpushしちゃうので気をつけてください。
if [ "$IS_PULL_REQUEST" = "false" ] && [ "$BRANCH" = "master" ]; then docker tag さっきビルドしたやつ hkdnet/awesome_image:master; docker push hkdnet/awesome_image:master; fi;
最初は混乱したこの2つの仕様ですが、以下のように考えたらなんとなく納得できるようになりました。
PR作成 → ということは動作確認の機会がある → これからマージされるやつと同じ環境変数でビルドしたイメージを作りたい
逆に "$IS_PULL_REQUEST" = "true" なら動作確認環境へデプロイみたいな感じのことをするとステキかもしれませんね。
*1:そらで書いてるんで違ったらごめんなさい