Github actions のコスパについて考えた
Github actions はとても便利だ。テストやビルドを自動化するのに活用している。
パブリックリポジトリだと無料で実行環境が利用できるのがありがたい。
その無料の実行環境 Github-hosted runner では重すぎる処理を実行したくて
Github actions の Self-hosted runner 環境を作った話は前回のエントリで書いた。
環境構築の動機となった目的は果たしたものの、作った環境はコスト性能比的にも良い選択だったのだろうか?
と思ってちょっと調べてみた。
今回はそれについて記す。
レイヤ数の多い大きな Docker イメージのビルドをギリギリ Github-hosted runner で実行していたのだけど、マルチアーキテクチャビルドをしようとして遂に処理できなくなった。仕方がないので、Self-hosted runner 環境を作ってビルドできるようにした。
イメージのビルドにはプログラムのコンパイルなど計算量の多い処理が含まれているのでメモリよりも CPU の速度が全体の処理速度を律速すると思われる。
目的のビルドを行うためには Github-hosted runner 環境より高性能な Self-hosted runner 環境にしないといけない。
かといってオーバースペックにして余計なコストもかけたくない。
ということで、Github-hosted runner の環境と同じ 2 コア CPU を利用できるいくつかのサービスを比較してみた。
同様の構成で最も安く高性能な CPU を利用できるのはどれなんだろうか? と。
ターゲットとしている Docker イメージのビルドではそれほど巨大なメモリが必要というわけではなさそうだったので比較対象構成のメモリ量の差は気にしないこととした。
なお、本エントリでの AWS ついての記述については 2022 年 5 月初旬のバージニアリージョンにおけるスペックや料金である。 Github についても 2022 年 5 月初旬時点でのスペック及び料金である。
Github-hosted runner の CPU と料金
まず Github-hosted runner の CPU と料金について確認した。
Linux の環境のスペックについてドキュメントには次の様に記載されている。
- 2-core CPU
- 7 GB of RAM memory
- 14 GB of SSD disk space
数量はわかるがどのような CPU の 2-core なのかがわからない。
そこで次の様な Github workflow で lscpu
を実行してみた。
name: lscpu on: push: branches: - test-workflow jobs: lscpu: runs-on: ubuntu-latest steps: - run: lscpu
その結果、次の型式の CPU であることがわかった。
パブリックリポジトリなら無料で使えるが、プライベートリポジトリなら有料で $0.008/min かかる。 時間あたりに換算すると $0.48/hour になる1。
コスト意識高く AWS EC2 インスタンスを利用している人なら、意外と高いと思う額だと思う。
まあ、結構な無料枠があるので個人利用なら実質無料で使っている人が多いと思う。
lscpu の詳しい結果を見る
Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 46 bits physical, 48 bits virtual CPU(s): 2 On-line CPU(s) list: 0,1 Thread(s) per core: 1 Core(s) per socket: 2 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 85 Model name: Intel(R) Xeon(R) Platinum 8272CL CPU @ 2.60GHz Stepping: 7 CPU MHz: 2593.907 BogoMIPS: 5187.81 Hypervisor vendor: Microsoft Virtualization type: full L1d cache: 64 KiB L1i cache: 64 KiB L2 cache: 2 MiB L3 cache: 35.8 MiB NUMA node0 CPU(s): 0,1 Vulnerability Itlb multihit: KVM: Mitigation: VMX unsupported Vulnerability L1tf: Mitigation; PTE Inversion Vulnerability Mds: Mitigation; Clear CPU buffers; SMT Host state unknown Vulnerability Meltdown: Mitigation; PTI Vulnerability Spec store bypass: Vulnerable Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Vulnerability Spectre v2: Mitigation; Retpolines, STIBP disabled, RSB filling Vulnerability Srbds: Not affected Vulnerability Tsx async abort: Mitigation; Clear CPU buffers; SMT Host state unknown Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology cpuid pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti fsgsbase bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f avx512dq rdseed adx smap clflushopt avx512cd avx512bw avx512vl xsaveopt xsavec xsaves md_clear
余談だけど、Geekbench Browserで Intel(R) Xeon(R) Platinum 8272CL の測定結果を探すと Azure 上で測定されたものばかりだった。この CPU はひょっとして Azule 専用?と思った。そうなら Github というのはやはり Microsoft 傘下なんだなと2。
Fargate の CPU と料金
続いて Fargate について調べてみた。
次のスペックで Github runner を実行して、workflow 内で lscpu
を実行してみた。
- Runtime version: 1.40
- cpu: 2048 core (= 2vCPU)
- mem: 8192 MB (= 8GB)
メモリについては選択できる値が決まっているので Github に近い値を選択した。
試してみると Fargate はいくつかの CPU の型式が混在しているらしい。起動のたびにランダムに割り当てられる様子。
数回の試行で次の2種類の CPU を確認した。
lscpu の詳しい結果を見る
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz だった場合の結果
Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 46 bits physical, 48 bits virtual CPU(s): 2 On-line CPU(s) list: 0,1 Thread(s) per core: 2 Core(s) per socket: 1 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 85 Model name: Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz Stepping: 7 CPU MHz: 3099.801 BogoMIPS: 4999.98 Hypervisor vendor: KVM Virtualization type: full L1d cache: 32 KiB L1i cache: 32 KiB L2 cache: 1 MiB L3 cache: 35.8 MiB NUMA node0 CPU(s): 0,1 Vulnerability Itlb multihit: KVM: Vulnerable Vulnerability L1tf: Mitigation; PTE Inversion Vulnerability Mds: Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown Vulnerability Meltdown: Mitigation; PTI Vulnerability Spec store bypass: Vulnerable Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Vulnerability Spectre v2: Mitigation; Retpolines, STIBP disabled, RSB filling Vulnerability Srbds: Not affected Vulnerability Tsx async abort: Not affected Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves ida arat pku ospke
Intel(R) Xeon(R) Platinum 8175M CPU @ 2.50GHz だった場合の結果
Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 46 bits physical, 48 bits virtual CPU(s): 2 On-line CPU(s) list: 0,1 Thread(s) per core: 2 Core(s) per socket: 1 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 85 Model name: Intel(R) Xeon(R) Platinum 8175M CPU @ 2.50GHz Stepping: 4 CPU MHz: 3106.294 BogoMIPS: 4999.99 Hypervisor vendor: KVM Virtualization type: full L1d cache: 32 KiB L1i cache: 32 KiB L2 cache: 1 MiB L3 cache: 33 MiB NUMA node0 CPU(s): 0,1 Vulnerability Itlb multihit: KVM: Vulnerable Vulnerability L1tf: Mitigation; PTE Inversion Vulnerability Mds: Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown Vulnerability Meltdown: Mitigation; PTI Vulnerability Spec store bypass: Vulnerable Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Vulnerability Spectre v2: Mitigation; Retpolines, STIBP disabled, RSB filling Vulnerability Srbds: Not affected Vulnerability Tsx async abort: Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves ida arat pku ospke
このスペックのバージニアリージョン(us-east-1)での料金は次の通り。
- cpu: $0.04048/core・hour x 2core = $0.08096/hour
- mem: $0.004445/GB・hour x 8GB = $0.03556/hour
- strage: $0.000111/GB・hour x 14GB = $0.001554/hour
- total: 0.08096 + 0.03556 + 0.001554 = $0.118074/hour ≒ $0.118/hour
Fargate Spot だと単価が下がるので次の値段になる。
- cpu: $0.0128568/hour x 2 = $0.0257136/hour
- mem: $0.00141177/GB・hour x 8 = $0.01129416/hour
- strage: $0.000111/GB・hour x 14GB = $0.001554/hour
- total: 0.0257136 + 0.01129416 + 0.001554 = $0.03856176/hour ≒ $0.039/hour
最大 70% 割引とのことだが、計算してみると約 68.24% OFF となっている。
ちなみに現状 Spot が使えるのは x86_64 のみで、Arm64 では提供されていない。
Codebuild の CPU と料金
比較対象として Github actions と同様 CI/CD に特化したサービスと位置づけられる CodeBuild も調べてみる。
CodeBuild には CPU、メモリともに Github-hosted runner とほぼ一致スペックのものが提供されないので、CPU コア数の一致する次のコンピューティングインスタンスタイプで確認した。
- general1.small (2 vCPU, 3 GB メモリ)
イメージは aws/codebuild/standard:5.0 (Ubuntu 20.04) を利用した。
CodeBuild も1種類の CPU で統一されているわけではなかった。 数回試行した結果、次の CPU のいずれかであった。
lscpu の詳しい結果を見る
Intel(R) Xeon(R) Platinum 8275CL CPU @ 3.00GHz の結果
Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 46 bits physical, 48 bits virtual CPU(s): 2 On-line CPU(s) list: 0,1 Thread(s) per core: 2 Core(s) per socket: 1 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 85 Model name: Intel(R) Xeon(R) Platinum 8275CL CPU @ 3.00GHz Stepping: 7 CPU MHz: 3614.580 BogoMIPS: 5999.99 Hypervisor vendor: KVM Virtualization type: full L1d cache: 32 KiB L1i cache: 32 KiB L2 cache: 1 MiB L3 cache: 35.8 MiB NUMA node0 CPU(s): 0,1 Vulnerability Itlb multihit: KVM: Vulnerable Vulnerability L1tf: Mitigation; PTE Inversion Vulnerability Mds: Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown Vulnerability Meltdown: Mitigation; PTI Vulnerability Spec store bypass: Vulnerable Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Vulnerability Spectre v2: Mitigation; Full generic retpoline, STIBP disabled, RSB filling Vulnerability Srbds: Not affected Vulnerability Tsx async abort: Not affected Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves ida arat pku ospke
Intel(R) Xeon(R) Platinum 8124M CPU @ 3.00GHz の結果
Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 46 bits physical, 48 bits virtual CPU(s): 2 On-line CPU(s) list: 0,1 Thread(s) per core: 2 Core(s) per socket: 1 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 85 Model name: Intel(R) Xeon(R) Platinum 8124M CPU @ 3.00GHz Stepping: 4 CPU MHz: 3412.932 BogoMIPS: 5999.99 Hypervisor vendor: KVM Virtualization type: full L1d cache: 32 KiB L1i cache: 32 KiB L2 cache: 1 MiB L3 cache: 24.8 MiB NUMA node0 CPU(s): 0,1 Vulnerability Itlb multihit: KVM: Vulnerable Vulnerability L1tf: Mitigation; PTE Inversion Vulnerability Mds: Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown Vulnerability Meltdown: Mitigation; PTI Vulnerability Spec store bypass: Vulnerable Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Vulnerability Spectre v2: Mitigation; Full generic retpoline, STIBP disabled, RSB filling Vulnerability Srbds: Not affected Vulnerability Tsx async abort: Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves ida arat pku ospke
1時間あたりの料金は次のとおり。
- general1.small: $0.005/min x 60min = $0.3/hour
なお、general1.small には 100min/月(= 1.67 hour/月)の無料枠が設定されており、この範囲内では無料。
Self-hosted runner (c6i.large) の CPU と料金
先日構築した Docker イメージのビルド用の Self-hosted runner 環境で利用している AWS EC2 についても確認した。
インスタンスタイプはコンピューティング最適化の c6i.large(2vCPU, 4GiB メモリ)を利用している。
次の CPU を使っていることがわかった。
lscpu の詳しい結果を見る
Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 46 bits physical, 48 bits virtual CPU(s): 2 On-line CPU(s) list: 0,1 Thread(s) per core: 2 Core(s) per socket: 1 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 106 Model name: Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz Stepping: 6 CPU MHz: 3502.020 BogoMIPS: 5799.96 Hypervisor vendor: KVM Virtualization type: full L1d cache: 48 KiB L1i cache: 32 KiB L2 cache: 1.3 MiB L3 cache: 54 MiB NUMA node0 CPU(s): 0,1 Vulnerability Itlb multihit: Not affected Vulnerability L1tf: Not affected Vulnerability Mds: Not affected Vulnerability Meltdown: Not affected Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl and seccomp Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Vulnerability Spectre v2: Mitigation; Enhanced IBRS, IBPB conditional, RSB filling Vulnerability Srbds: Not affected Vulnerability Tsx async abort: Not affected Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single ssbd ibrs ibpb stibp ibrs_enhanced fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves ida arat avx512vbmi pku ospke avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg tme avx512_vpopcntdq rdpid md_clear flush_l1d arch_capabilities
このインスタンスタイプの1時間あたりのオンデマンド料金は $0.085/hour。 スポットインスタンスだと大体 50〜60% OFF 程度の割引率なのでざっくり半額になると期待していいと思う3。
比較
Github-hosted runner の実行環境とそのスペックに相当する実行環境を作れる AWS サービスのそれぞれの CPU と1時間あたりの料金は下記の表の通り。
いずれも CPU のブランドは Intel(R) Xeon(R) Platinum。
AWS の料金はバージニアリージョン(us-east-1)のもの。
Geekbench の値は Geekbench Browser から取ってきた値で、同じ型式の CPU の結果でメモリ等近しい構成の Multi-Core Score。CPU の性能はクロック数のみで決まるものでもないので比較の指標として追加した。あくまで参考値として。
環境 | 料金 ($/hour) |
CPU | クロック数 | コア数 | Memory | Geekbench (参考値) |
---|---|---|---|---|---|---|
Github-hosted runner | 0.480 | 8272CL | 2.60GHz | 2 | 7GB | 1608 |
Fargete | 0.117 | 8259CL 8175M |
2.50GHz 2.50GHz |
2 | 8GB | 1900 -- |
Fargete Spot | 0.039 | 8259CL 8175M |
2.50GHz 2.50GHz |
2 | 8GB | 1900 -- |
CodeBuild (general1.small) | 0.300 | 8275CL 8124M |
3.00GHz 3.00GHz |
2 | 3GB | 2329 2256 |
EC2 c6i.large(オンデマンド) | 0.085 | 8375C | 2.90GHz | 2 | 4GB | 2872 |
EC2 c6i.large(SPOT) | 0.043 | 8375C | 2.90GHz | 2 | 4GB | 2872 |
やはり Github-hosted runner 環境が最も料金が高い。CPU 性能的にはほぼ同等と思われる Fargate の 4 倍以上となっている。比較的高価と思っていた CodeBuild よりも高い。
単純に時間あたりの料金を比べるとそうなのだが、ちゃんとコスパを比較するなら無料枠を考慮する必要がある。
無料枠を考慮して時間と利用料の関係をグラフ化すると次の様になる。
パブリックリポジトリでの利用や無料枠に収まる範囲での利用しかないなら Github-hosted runner は最安で最も手間も少ないのは言うまでもない。
無料枠を超えている場合、目安として Github の Free アカウントなら総利用時間が 45 時間/月、Pro アカウントやオーガニゼーション(Team アカウント)なら 60 時間/月を超えていると Self-hosted runner の利用を検討してもいいかと思う。
Fargate は Github-hosted runner よりちょっと性能が良さそうってところだが、ほぼ同じ性能を安く調達したいだけなら魅力的だ(EC2 より起動が速いので)。
コストを抑えるには workflow の実行毎に Self-hosted runner のインスタンスを起動し、終わったら止めることが前提になるが、自動化すればその手間は負担にならないと思う。
単純に Github-hosted runner でも実行できる処理のコスト改善をするだけなら上記の通り。
ただ、利用時間が長い場合、それは実行回数が多いためではなく1回の処理時間が長くなっているのではないだろうか。そうならばより性能の高いインフラで Self-hosted runner 環境を作ったほうが良いだろう。
例えば Linter の実行など軽い処理を Github-hosted runner で実行して無料枠を有効に消費しつつ、ビルドやコンパイルなど比較的重くて時間がかかっている処理は Self-hosted runner の利用を利用するといいかもしれない。
ユニットテストなどはコア数の多いインスタンスを使って並列実行すれば大きな時間短縮も狙えると思う。
クラウドサービスを利用すると実際には通信料や構築と運用という目に見えにくいコストもかかるが、それでもコストと処理時間の両方を削減できる可能性もあると思う。
こうゆう用途には処理内容に適した EC2 インスタンスを選択して Self-hosted runner を運用するのがいいと思う4。Github-hosted runner と同じ金額を払うならもっと性能の良いインスタンスを利用できるし、スポットインスタンスを使えば更にコストを下げられる5。
まとめ
Github-hosted runner では重くて実行できなくなった Docker イメージのビルドのために Self-hosted runner の実行環境を作ったがもっと良い選択肢があったのではと気になって調べてみた。
結果的には ECS の EC2 ノードで Self-hosted runner コンテナのタスクを運用するという自分の選択がベストかなと思われた。
Github actions は便利だけど無料枠を超えて使っている場合、利用料は決して安くないということも再認識した。
とはいえ別の環境を構築、運用するにも手間暇がかかるのでコストとバランス次第だと思う。
Self-hosted runner 環境を構築するなら私的には現時点で次の結論を得た。 - 処理に見合った CPU 性能を求めるなら EC2 のスポットインスタンスで ECS タスクを利用する - Github-hosted runner と同程度の性能でよく、コストを下げたいなら Fargate Spot を使う
なお、本エントリで挙げた利用料には通信料金など運用に伴い発生するすべての料金を含んでいるわけではない。
また、私の計算間違いで値が不正確な可能性もある。
したがって実際の運用でかかる料金について一切保証できないので悪しからず。
と言い訳を用意しつつ締めくくる。
参考
- About GitHub-hosted runners - GitHub Docs
- 料金 - AWS Fargate | AWS
- 料金 - AWS CodeBuild | AWS
- オンデマンドインスタンスの料金 - Amazon EC2 (仮想サーバー) | AWS
-
Github の Free アカウントで 2000 分/月(= 33.3 時間/月)、Pro アカウントでは 3000 分/月(= 50 時間/月)の無料枠がある。プライベートリポジトリでもこの枠内の利用であれば無料。↩
-
更に余談だけど、Gitlab の CI/CD で提供されている SaaS 環境はドキュメントに GCP の n1-standard-1 を使っていると記載されている。↩
-
さらに利用が増えれば Github-hosted runner より CodeBuild の方が安くなるが、乗り換える気があるならその前に Self-hosted runner を導入しているだろう。その手間ひまかけて作ったその環境をわざわざより高額な CodeBuild に乗り換えることもまあないかなと思う。↩
-
$0.48/hour も出せばオンデマンド料金でも AWS EC2 の t3.2xlarge(8vCPU 32GiB $0.3328/hour)や c6i.2xlarge(8vCPU 16GiB $0.34/hour)などが使える。スポットならざっくり半額と考えればさらに高価だが高性能なインスタンスの利用も検討できるかもしれない。↩
ECS で Github Actions Self-hosted runner を動かす
前回のエントリで Github Actions の Self-hosted runner を試したことを書いた。
で、もともと何がやりたかったかというと、Github workflows の実行環境ではビルドできないほど大きな次の Docker イメージのビルド。イメージのサイズは約 10 GB にもなる。
毎回 EC2 インスタンスを起動しその中で Self-hosted runner を起動するのも面倒なので ECS で環境構築した。
それについて記す。
実現したいこと
もともとは Github workflow で Docker イメージをビルドし、それを GHCR1 に Push していた。
これまでなんとかビルドできていたが、イメージが大きくなりすぎて難しくなってきた。そもそもの欲張ったイメージの仕様上、小さくするのも難しい。
さらに Apple Silicon の MBP に乗り換えたので Arm 向けのイメージもビルドしたくなり、マルチアーキテクチャのビルドを追加したら遂にビルドできなくなってしまった2。
それでビルド環境の増強でなんとかしたい。というのが要件だ。
整理すると次を実現したい。成果物である Docker イメージはこれまで同様 GHCR に Push したい。
- 大きな Docker イメージをビルドしたい
- x86_64 と arm64 の両方のアーキテクチャ向けにビルドしたい
- できるだけビルド時間は短くしたい
- 手間を少なく運用したい
- コストをできる限り減らしたい
MUST 要件は 1、2 のみ。 残りはより良いソリューションとするための検討課題。
Self-hosted runner を採用したこと
「大きな Docker イメージをビルドしたい」ので Github がホストするランナー3よりパワフルな環境を用意する必要がある。
当初は Self-hosted runner ではなく AWS Codebuild で実現しようかとも考えた。慣れているし。
しかし検討する中で、Self-hosted runner の方が「手間を少なく運用したい」を満たしやすいと考えた。
ビルドがいつも想定内の実行時間でうまく動けばいいのだろうけど、時間がかかったりエラーが発生するケースがあるかもしれない。 そうすると進捗やログの確認がしやすいほうがよい。CodeBuild だと、AWS Console を開いて見ることになる4けど、Self-hosted runner なら Github だけで完結する。
ということで Self-hosted runner を使うことに決めた。
また通常運用では「管理を Github 内で完結させるという」のも今後の検討の方針とした。
実行環境は EC2 ノードの ECS にした
さて、Self-hosted runner を使うとするとそれを動かす環境はどうしよう。
オンプレでリソースを用意なんてできないのでクラウドサービスを使う。
各種サービスの無料枠では Github の提供しているもの以上の環境を得られそうにないので、そこの課金はやむなしと考える。が、「コストをできる限り減らしたい」。
とすると利用する時間だけ起動し終わったら速やかに止めるようにしたい5。
結論としては以下のように考えて AWS ECS を EC2 ノードで使うこと選択した。
シンプルには前回のエントリでやったように EC2 インスタンス上で実行すればいい。いちいちインスタンスの中に入って操作するのは面倒なら専用の AMI を作ればインスタンス起動時に Github runner を自動起動するものが作れるだろう。
しかし一方で、今後 Github runner もアップデートされていくだろう。それを考えると AMI よりも Docker イメージのメンテナンスの方がローカルマシン上でも動作確認できるし「手間を少なく運用したい」をより満たすだろう。
ということで、コンテナで運用することにした。
AWS でコンテナを動かす環境はいくつかあるが、 EKS クラスターを作るのは要件に対して大げさすぎし、クラスターが存在するだけで課金されるので「コストをできる限り減らしたい」的に NG。
App Runner も Web システムを動かすことに特化されすぎているし、
Lambda は最大 15 分でタイムアウトしてしまう。実行したい処理はおそらくその制限内では収まらない。
ということで ECS を使うことにした。
ECS を使うなら Fargate の方がクラスターノードのリソース管理が不要でいいかと思った。 が、Fargate の良くないところは CPU やメモリの数量は選べるが、その性能は選択できないところ。 実際に Fargate で提供される CPU と Github actions の実行環境のそれを比べてみると同等かやや劣っていた。 Github よりパワフルな環境を手に入れるための Self-hosted runner なのでこれでは本末転倒。
ということでインスタンスタイプで性能の良いものを選択できる EC2 を利用することとした。 実際、構築してみるとタスクを起動するとキャパシティプロバイダとオートスケーリンググループにより自動的にインスタンスがクラスターに追加できる。そしてタスクを削除すると追加されたインスタンスが停止される(タスクがなくなれば、インスタンスも 0 台になる運用も可能)。直接的にインスタンスの運用はしなくていいので手間的には Fargate と変わりなかった。
ECS で Docker イメージをビルドするには
Github からは公式のイメージは公開されていないので Github runner のイメージは自分で作成した。
Docker in Docker でイメージをビルドするにはコンテナを特権モードで動かす必要がある6。タスク定義で privileged: true
を設定し、Docker をインストールしたイメージを作れば良い。
ECS コンテナ内で Docker を使うにはもう1つ方法がある。ホストマシン側の Docker エンジンにコンテナからアクセスする方法があり今回はそれを利用した。コンテナ側に Docker エンジンインストールするとイメージが大きくなりそうだし、特権モードも必要ないし。
やり方は次の通り。
Docker イメージに次を含める。
- Docker エンジンは不要だけどコマンドは必要なので docker-ce-cli はインストールする
GID=994
の docker という名前のユーザグループを作るdocker
を実行するユーザを作る。その際、ユーザグループ docker に入れる
Dockerfile の抜粋で示すと次の通り。
ARG USERNAME=user ARG GROUPNAME=user ARG UID=1000 ARG GID=1000 ARG DOCKER_GROUPNAME=docker ARG DOCKER_GID=994 RUN apt-get update \ && apt-get install -y --no-install-recommends \ docker-ce-cli ## Add user and workdir RUN groupadd -g ${GID} ${GROUPNAME} && \ groupadd -g ${DOCKER_GID} ${DOCKER_GROUPNAME} && \ useradd -m -s /bin/bash -u ${UID} -g ${GID} -G ${DOCKER_GID} ${USERNAME} USER ${USERNAME}
docker ユーザグループの GID が決め打ちすぎるのが気になるが、とりあえずココが原因でエラーになったりしてないので良しとする。
そして、タスク定義のコンテナ定義でホストの /var/run/docker.sock を同じパスでマウントする7。
これで ECS のコンテナ内でホスト側(つまり、コンテナ自身の起動に利用されている)Docker エンジンを使える。
あとは、actions/runner: The Runner for GitHub Actions から runner を取ってきてインストールすれば Docker ビルドできる Self-hosted runner イメージの出来上がり。
Dockerfile 全体はこちら => Dockerfile
ちなみにこのイメージも x86_64 と Arm64 で使いたいのでマルチアーキテクチャでビルドしている。
ビルドステップが少ないのでこちらは Github hosted な runner で十分ビルドできる。
作ったもの
作ったものの全体は次の様の通り。
これを次のように使っている。
- 予め起動用の Lambda 関数で Github runner の ECS タスクを起動する
- ビルド対象のリポジトリで workflow を Push などのトリガーで実行する
- workflow の Job は x86/arm64 それぞれのインスタンス上の Github runner タスクが並行して Docker イメージをビルドし GHCR に Push する
- 後続の Job でマルチプラットフォームとしてマニフェストを作成し、GHCR に Push する
- 終わったら停止用 Lambda 関数を使って Github runner の ECS タスクを止める
前述したが ECR クラスターのノードとなる EC2 インスタンスの起動/停止は直接操作していない。Capacity Provider と Auto Scaling Group がよしなに用意してくれる。
これらを CDK で構築した。そのリポジトリは次の通り。
これで要件の「大きな Docker イメージをビルドしたい」と「x86_64 と arm64 の両方のアーキテクチャ向けにビルドしたい」を実現した。Buildx を使ったマルチプラットフォームビルドだといずれかのイメージがエミュレーション環境でビルドされるので時間がかかるのだが、それぞれネイティブな環境を用意したのでエミュレーションオーバーヘッドによる遅さはなくなった。1時間以上かかっていたビルドが約 30 分に短縮できた。これには C6i/g.large というパワフルなインスタンスを使ったおかげもあるが、スポットインスタンスを使うことで、「できるだけビルド時間は短くしたい」を満たしながら「コストをできる限り減らしたい」もできていると思う。 はじめはタスクの起動/停止を簡便化する Lambda を用意していなかったのだが「手間を少なく運用したい」ので追加した。
Self-hosted runner とそれを使った workflow の動き
前回のエントリ(Github Actions の Self-hosted Runner をやってみた)では単に Self-hosted runner を起動してハロワ程度を試しただけだったが、実際に使うものを構築しながらもう少し詳しく調べてみた。
Self-hosted runner のマシンは外部にポートをさらさなくて良い
ドキュメントにも書かれているのだけど、runner は HTTPS long poll により Job の情報を受け取り常に runner 側から Github に通信する。runner にインターネット側から接続できるようにする必要はない。
実際、インターネットとの間に NAT がある家庭用ネットワークの中でも動く。
とすると AWS でも外部からの切断を遮断された Private Subnet で運用できると思う。
しかし、コストを減らすために Public Subnet で運用している。
Private subnet に置いても wrokflow の中で各種ライブラリパッケージや Docker イメージ等をダウンロードすると思うので runner からはインターネット側に通信できる必要がある。
そのためには NAT Gateway が必要になるが NAT Gateway は存在するだけで料金がかかるし、それ経由の通信が割高だからだ。
まあ、Public IP が付くとはいえ、外部からの接続は制限しているし、起動しているのは Job 実行の間だけだし良いだろう。
workflow の実行時に Self-hosted runner は起動していなくても良い
workflow から Self-hosted runner を使う Job を実行する前に runner を起動しておいたほうがスムーズではあるば、予め起動しておくことは MUST ではない。
workflow の Job は一旦キューイングされる。なので、キューイング時点で runner が起動していなくても runner が起動した時点でキューから Job を取得し処理される。なおドキュメント によると最大 24 時間キューに留めることができる。
今は手動で Self-hosted runner のタスクを上げ下げしているが、運用の仕方がパターン化してくれば自動化してもいいかもしれないと思っている。runner を起動する Job を追加して ephemeral (Job を処理したら自ら終了する)モードの runner の起動すれば Job 終了とともに runner も終了するので起動/停止が自動化できそう。
自動アップデート機能搭載
Github runner は自動アップデート機能を備えている。
以前はうまく起動していた runner の ECS タスクが起動直後に落ちるようになったので調べると起動時にアップデートがあれば自動的にダウンロードして、再起動していた。
再起動のためにプロセスが終了するのでその時点でコンテナも終了し、タスクが落ちていたというわけだ。
それ自体は便利な機能なのだけど、コンテナでの運用を考えるとそのためにコンテナが落ちるのは困る。
自動アップデートを無効化するには runner を設定する際に --disableupdate
オプションを付ければよい。
まとめ
Self-hosted runner を利用してビルドできるようになった。
それだけでなくビルド時間も1時間以上 → 約 30 分と短縮できた。
管理も Github 側で Job の進行状況も確認できるし、ログも参照できる。
運用の手間も最小にできたと思うし、自動化の目処もある。
安いリージョン(us-east-1)での運用とスポットインスタンスの利用でコストも抑えられたと思う。
参考
- Hosting your own runners - GitHub Docs
- actions/runner: The Runner for GitHub Actions
- FargateのvCPU性能と価格感等雑感
-
GitHub Container Registry↩
-
エラーが発生したりはないが、ビルド時間が Github action の実行時間制限を超えてしまった。時間がかかるのは Arm64 向けのビルドが QEMU のエミュ環境になるからだと思われる。↩
-
AWS Console はログインが維持される時間が長くないのでしょっちゅうログイン操作を求められている。なので進捗やログ確認目的で開くのはめんどくさい。↩
-
この点においてはリソース確保と開放を自動でやってくれる CodeBuild は便利だと思う。↩
-
Fargate はそもそも特権モードでコンテナ起動できないのも採用できない理由だった。しかしkaniko という OSS を使うと特権モードでなくてもイメージをビルドできるらしい(Amazon ECS on AWS Fargate を利用したコンテナイメージのビルド | Amazon Web Services ブログ)。kaniko でイメージをビルドする Github action もあるようなのでそのうち試してみたい。↩
-
https://github.com/HeRoMo/ecs-github-actions-runner/blob/f980828be1b49e5590f174471e20b9f105c83027/lib/constructs/EcsGithubActionsRunner.ts#L148-L155↩
Github Actions の Self-hosted Runner をやってみた
個人的に時々更新している次の Docker イメージがある。
そのサイズ、約 10GB。 もちろんビルドにも時間がかかる。
これまで Github workflow でビルドし、 Github Container Registory (ghcr) に push して公開していた。
もともとビルドに1時間以上かかっていたが、x86_64 に加えて Arm64 向けにもビルドしようとしてできなくなった。Github workflow はデフォルトでは6時間でタイムアウトし、強制終了となる。その6時間の内に終わらなくなってしまったのだ。
Public なリポジトリだと無料なので少々時間がかかってもまあいいかと長過ぎるビルド時間を改善せず放置していたのだがビルドできないのは困る。
AWS CodeBuild に乗り換えようかなとも思ったのだけど、以前から気になっていた Github Actions の Self-hosted Runner を試してみた。
Self-hosted Runner を試す
まずはどんなものか知るために EC2 インスタンスを立てて試してみた。
本エントリではそれについて記す。
リポジトリでの登録
最初に Self-hosted runner を利用したいリポジトリの Setting で新たな self-hosted runner を登録する。
次の図の①②③の順でクリックするだけ。
③の緑のボタンをクリックすると次のページが表示される。
このページには Download 方法と Configure 方法が記載されている。 Download 方法はインストール手順、Configure 方法は起動方法と思って OK。
ちなみに macOS と Windows は x64 アーキテクチャしかないが、 Linux は x64、ARM、ARM64 が選択できる。
重要なのはアンダーラインを引いた token の値1。後で必要なのでメモしておく。
インストール
EC2 インスタンスに SSH で接続して前述のページに記載されている通りのコマンド2を実行するだけ。 迷うところはない。
mkdir actions-runner && cd actions-runner curl -o actions-runner-linux-x64-2.290.1.tar.gz -L https://github.com/actions/runner/releases/download/v2.290.1/actions-runner-linux-x64-2.290.1.tar.gz tar xzf ./actions-runner-osx-x64-2.290.1.tar.gz
起動
tar ファイルを開いたら、前述のページの Configure に記載されているコマンドを実行する。
そのままコピー&ペーストで構わない。
./config.sh
実行後、いくつか質問されるがとりあえず全部デフォルトでいいのでリターンしていくだけ。
[ec2-user@ip-172-31-26-219 actions-runner]$ ./config.sh --url https://github.com/HeRoMo/jupyter-langs --token XXXXXXXXXXXXXXXXXXXXXXXXXXXXX -------------------------------------------------------------------------------- | ____ _ _ _ _ _ _ _ _ | | / ___(_) |_| | | |_ _| |__ / \ ___| |_(_) ___ _ __ ___ | | | | _| | __| |_| | | | | '_ \ / _ \ / __| __| |/ _ \| '_ \/ __| | | | |_| | | |_| _ | |_| | |_) | / ___ \ (__| |_| | (_) | | | \__ \ | | \____|_|\__|_| |_|\__,_|_.__/ /_/ \_\___|\__|_|\___/|_| |_|___/ | | | | Self-hosted runner registration | | | -------------------------------------------------------------------------------- # Authentication √ Connected to GitHub # Runner Registration Enter the name of the runner group to add this runner to: [press Enter for Default] Enter the name of runner: [press Enter for ip-172-31-26-219] This runner will have the following labels: 'self-hosted', 'Linux', 'X64' Enter any additional labels (ex. label-1,label-2): [press Enter to skip] √ Runner successfully added √ Runner connection is good # Runner settings Enter name of work folder: [press Enter for _work] √ Settings Saved. [ec2-user@ip-172-31-26-219 actions-runner]$ ./run.sh √ Connected to GitHub Current runner version: '2.289.3' 2022-04-23 09:34:07Z: Listening for Jobs
./run.sh
実行後、Listening for Jobs
と表示されるが、これで Github に接続し、ワークフローの Job の実行を待っている状態。
先程 token を取るのに利用した Github の管理ページを確認すると次のように Self-hosted runner が idle
状態となっている。
ip-172.31.26.219 は runner の名前。何も指定していないので EC2 インスタンスのホスト名がそのまま使われている。
その横の 'self-hosted'、'Linux'、'X64' はこの runner を使う場合に指定するラベルでワークフローの runs-on
で指定する。
タスクの実行
試しに test-workflow といブランチを作り次のようなワークフローを追加してみる。
self-hosted runner で実行するために runs-on: self-hosted
と指定している。
ここはラベル 'Linux' または 'X64' を指定しても同じ。
name: Hello World on: push: branches: - test-workflow jobs: hello-world: runs-on: self-hosted permissions: contents: read packages: write steps: - run: echo "hello-world" - run: uname -a - run: sleep 60 # すぐに終了するとスクショが取りにくいので
これを Github に Push すると ./run.sh
を実行したターミナルには Job 実行を示すログが出色される。
[ec2-user@ip-172-31-26-219 actions-runner]$ ./run.sh √ Connected to GitHub Current runner version: '2.290.1' 2022-04-23 10:22:54Z: Listening for Jobs 2022-04-23 10:25:48Z: Running job: hello-world # ここから実行ログ 2022-04-23 10:26:58Z: Job hello-world completed with result: Succeeded # 成功終了したことを示すメッセージ
Github の管理ページではタスクの実行中は次のように Active
と表示される。
あら簡単。
ワークフローの実行が終了すると、runner は再び Idle
状態に戻る。
Runner の終了
./run.sh
を実行したターミナルで ctrl + c
で runner を止めることができる。
止めると管理ページでは次のように Offline
となる。
まとめ
Github Action の Self-hosted runner を試してみた。
思ったより随分簡単だった。
Github 側から Runner への接続はしないようなので Runner からインターネットへの通信ができれば動作できる様子。
ごく簡単なワークフローしか試さなかったが、Docker を使うなら予め使える環境を用意する必要があるだろう。
運用を考えると Runner 自体も Docker で動かしたいし、次回は ECS で Runner を動かしてみるつもり。
参考
Alfred の AlfredTweet をなんとか動かした
Mac を使っていると Alfred はとても便利。というか個人的には必須ツールと思っている。
無料で使っていても十分便利なのだけど、Powerpack に課金すると更に便利になる。
というのも Powerpack では Workflows がアンロックされるからだ。
Workflows はスクリプトにより Alfred を機能拡張できる仕組みで、自分で新しい Workflow を作成できるし、他のユーザが作成して公開しているものを利用できる。
例えば、Alfredtweet をインストールすると Alfred の UI から直接ツイートできる。
しかし、Alfredtweet は PHP で実装されているため、Monterey で PHP が OS に含まれないようになって動かなくなって残念に思っていた。
Alfred 4.6 で Alfred 自体が homebrew でインストールした PHP のパスも検知するようになった。
それによって多くの PHP を使った workflow は何にもしなくても動くようになったが、Alfredtweet はダメだった。
最初に Twitter アカウントの認証設定があるのだけどそれが動かないのでどうしようもない。
どうも作者もメンテを放棄している様子だし、別の代替を探してみた1のだけど見つけられなかった。
諦めていたのだけど、少し変更して動かせたのでメモしておく。
Alfredtweet のインストール
インストール自体は普通に AlfredTweet | Packal からダウンロードした alfredtweet.alfredworkflow を Alfred で開くだけ。
Twitterのアカウント認証設定
ツイートするためには Twitter アカウント認証を通す必要がある。
Alfred で AlfredTweet
コマンドを実行するとできるはずなのだけど、動かない。
ログを見ると php
コマンドが見つからないとエラーが出ている。
Workflows 管理画面で AlfredTweet を開いて、次のワークフローブロックをダブルクリックで開く。
次のダイアログが開く。動かない原因は、ここに直書きされている php
コマンドらしい。
これを次の様に Homebrew でインストールしたフルパスに書き換えてやる。
書き換えたら「save」ボタンで閉じる。
Alfredtweet のアカウント設定
上記の作業後、Alfred を開いて AlfredTweet
とコマンドする。
すると以前の様にブラウザで次の認証ページが開いて Twitter アカウントとの連携ができるようになる。
Twitter で権限認可したら遷移するページにトークンが表示される。再び Alfred を開いて AlfredTweet
の引数にトークンを渡して実行すると AlfredTweet にアカウント設定できる。
はずなのだが、ここでも動かない。 ログを見るとディレクトリが見つからないというエラーが出ているので、該当のディレクトリを次の様に作ってやる。
mkdir -p "~/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data"
ふう、これでなんとか動いたようだ。 なにやら次のようなメッセージが通知されたのだけど、これは無視しても OK だった。
tweet
コマンドの修正
さてと、試しにツイートしてみるかと Alfred を開いて tweet
コマンドを使ってみるとまだエラーで動かない。
ログを確認すると次の箇所が悪いらしい。
開いてみると案の定、php
コマンドを使おうとしている。
それを次の様に書き換えるとツイートできるようになった。
他のコマンドも動かない
Alfredtweet には他にも follow
/unfollow
や block
/unblock
などの操作もできる。
これらを開いてみると、書かれている Script は PHP なのに Language には /bin/bash
が設定されているので、/opt/homebrew/bin/php
に変更すると動くようになる。
他にタイムラインやメンション、リストなどを Alfred 上で確認するコマンドもあるのだけど、それらはデータは取ってこれているようだけど、レスポンスを正しくパースできなくなて目的通りには動かない。
まあ、ツイート以外は Twitter アプリ上で操作するほうが便利だと思うのでこれらのコマンドの不具合は放置することした。
なんとか動いたが、そのうち動かなくなるのだろうな。 まあ、ツイート自体もそんなに頻繁にする方ではないのだけど。
-
alfred-twitter-toolkitというのを見つけたのだけど、つぶやくことはできないっぽい。作者自ら'AlfredTweet is far more powerful' って書いてるし。↩
AWS Systems Manager を触ってみた
AWS Systems Manager (SSM)は AWS 上、オンプレミスに関係なく SSM エージェントが稼働するサーバインスタンスを一元管理するためのサービス。
存在は知っていて、パラメータストアのような一部の機能は使っていたものの、AWS コンソールからオンプレサーバを操作するっていうのがどこまでできるのか気になっていた。でやってみた。
このエントリは次の 3 日目の記事となる。
マネージドインスタンスの登録
本エントリでは AWS の外、つまり、AWS 以外のデータセンターや自宅などで稼働するサーバを物理、仮想を問わずオンプレサーバと呼ぶことにする。
SSM より管理されるサーバはマネージドインスタンスと呼ばれる。 マネージドインスタンスには SSM エージェントをインストールする必要がある。
順にやってみた。
アクティベーションの作成
最初に AWS の SSM コンソールでアクティベーションを作成する。 ここでいう「アクティベーションを作成する」とは、SSM エージェントをアクティベーションする際の「アクティベーションコードを発行する」ことである。
以下に手順を示す。
- AWS Systems Mangerのコンソールのアイティベーションにアクセスする。
- 右上の「アクティベーションの作成」をクリックする。
必要な項目を入力しアクティベーションを作成する
- 登録後表示される Activation CodeとActivation IDを控えておく。これがエージェントのインストールの際に必要となる。
- 再び参照できないのでなくさないようにする必要がある。
- なくしたらまたアクティベーションを作成すれば良いだけだが、面倒。
SSMエージェントのインストール
SSM エージェントは次の OS にインストールできる。
- Amazon Linux および Amazon Linux 2
- Ubuntu Server
- Red Hat Enterprise Linux (RHEL)
- CentOS
- SUSE Linux Enterprise Server (SLES) 12
- Raspbian
各 OS へのインストールは次の資料の手順が示されている。
このエントリでは Ubuntu 18.04 Server で試してみた。 Ubuntu の場合のインストール手順は次の通り。
mkdir /tmp/ssm curl https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/debian_amd64/amazon-ssm-agent.deb -o /tmp/ssm/amazon-ssm-agent.deb sudo dpkg -i /tmp/ssm/amazon-ssm-agent.deb sudo service amazon-ssm-agent stop sudo amazon-ssm-agent -register -code "activation-code" -id "activation-id" -region "region" sudo service amazon-ssm-agent start
この 5 行目で SSM エージェントのアクティベーションを行っている。 その際に先に作成したアクティベーションのActivation CodeとActivation IDを利用する。
試したところ全くつまずくところはなく、すんなりインストールできる。 設定に成功するとしばらくして次の様に AWS コンソールでマネージドインスタンスとして表示される。
マネージドインスタンスになれば、AWSコンソールからの操作を行うことができる。
シェルコマンドの実行
SSM エージェントを通して任意のシェルコマンドを実行できる。
ハローワールド代わりに、次のコマンドを実行してみた。
date ls -l /etc/
マネージドインスタンス上でコマンドを実行するには[アクション]-[ランコマンド]を利用する。 以下に手順を示す。
コマンドのドキュメント
実行できるコマンドは[共有リソース]-[ドキュメント]で定義され、管理されているようだ。
任意のシェルコマンドを実行させるには AWS-RunShellScript
ドキュメントを利用する。
ということで、最初に次の様に AWS-RunShellScript
を選択する。
コマンドのパラメータ
続いてコマンドのパラメータを設定する。
ここは、選択したコマンドにより異なるが、AWS-RunShellScript
の場合、次のような UI になっており、実行するシェルコマンドを直接記述する。
ターゲット
次に実行対象のマネージドインスタンスを選択する。
直接指定もできるが、マネージドインスタンスに設定したタグで指定すれば、複数インスタンスを一度に設定できるのであろう。
その他の設定
他に次の設定できる。詳細は省略。
- コメント
- 実行レートの制御
- 指定した複数のインスタンスを何%づつ処理するかの指定
- 出力オプション
次の出力先を指定できる。択一でなく複数出力も可能
- S3 バケット
- CloudWatch ログ
- SNS 通知
最後にはコンソールからではなく CLI で実行する場合のオプション設定済みの CLI コマンドも表示される。
実行
実行すると次の様に実行ステータスが表示される。
![SSM ランコマンド実行状況
全体の進行状況が表示されるとともに、個々のターゲットでの実行時の出力も確認できる。
結果の出力
コマンドの実行結果を確認する。 AWS コンソールの SSM ランコマンドのコマンド履歴で確認できる。
また、出力先に S3 を指定しているとコマンドの標準出力がファイルに出力される。 この例の場合、次の内容のファイルが指定バケットの指定ディレクトリに出力される。
2018年 12月 02日日曜日 16:15:24 JST 合計 796 drwxr-xr-x 4 root root 4096 11月 29 2017 X11 -rw-r--r-- 1 root root 2981 11月 29 2017 adduser.conf drwxr-xr-x 2 root root 4096 11月 5 20:53 alternatives 〜 以下略 〜
期待通りの結果が出力されている。
Dockerコンテナの起動
さて、個々のコマンドを実行してできることを確認するよりも Docker コンテナを起動できれば、各サーバのソフトウェア環境にかかわらず処理を実行させることができるであろうと考えて試してみた。
EC2 インスタンスではなく物理マシン上の Ubuntu 18.04 LTS server に SSM エージェントをインストールして、Docker コンテナを起動するまでを試みた。
Docker は apt
でインストールする
SSM エージェントを通して次のコマンドを実行してみる。
echo $PATH
すると出力結果は次の通り。これが SSM エージェントに通っている PATH ということになる。
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
一方、Ubuntu 18.04 LTS server では最近追加された snap
の利用を促される。
Docker も snap
でインストールするよう促されるが、それに従うと /snap/bin/docker
にインストールされてしまう。が、SSM エージェントのパスに /snap/bin
が追加されるわけでもなく Docker コマンドが見なからないという結果になった。
apt
でのインストールもできるので、そちらでインストールすると
パスの通ったところに Docker がインストールされるので snap
ではなく apt
でインストールする必要がある。
Dockerコンテナの起動には AWS-RunShellScript
を使う
SSM には AWS-RunDockerAction
ドキュメントというその用途用のドキュメントが存在する。しかしそれを利用した場合、次の様にイメージの pull には成功しているようだが、コンテナの起動には失敗してしまった。
Unable to find image 'nginx:latest' locally latest: Pulling from library/nginx f17d81b4b692: Pulling fs layer 82dca86e04c3: Pulling fs layer 046ccb106982: Pulling fs layer 046ccb106982: Verifying Checksum 046ccb106982: Download complete f17d81b4b692: Download complete 82dca86e04c3: Verifying Checksum 82dca86e04c3: Download complete f17d81b4b692: Pull complete 82dca86e04c3: Pull complete 046ccb106982: Pull complete Digest: sha256:d59a1aa7866258751a261bae525a1842c7ff0662d4f34a355d5f36826abc0341 Status: Downloaded newer image for nginx:latest docker: Error response from daemon: OCI runtime create failed: container_linux.go:348: starting container process caused "exec: \"\": executable file not found in $PATH": unknown. failed to run commands: exit status 127
一方、汎用のシェルコマンド実行用の AWS-RunShellScript
ドキュメントを利用して次を実行すると Docker コンテナの起動に成功した。
項目 | 設定値 |
---|---|
コマンドのドキュメント | AWS-RunShellScript |
commands | docker run --name some-nginx -d -p 8080:80 nginx |
これは nginx を起動するものだが、別マシンからの nginx へのアクセスも可能であった。
AppArmer
もう1つ問題があった。
Ubuntu18.04 では AppArmer が有効になっている。
そのため、起動したコンテナを止められない事象が発生した。
次のコマンドで AppArmer をとめるとコンテナを停止できる。
sudo aa-status # 状態の確認 systemctl disable apparmor.service --now service apparmor teardown
上記の実行後、次の設定で起動した Docker コンテナを停止できた。
項目 | 設定値 |
---|---|
コマンドのドキュメント | AWS-RunShellScript |
commands | docker stop some-nginx |
実運用の際には AppArmer を止めるか、適切な設定が必要になるだろう。
まとめ
- SSM エージェントを Ubuntu にインストールして、SSM コンソールから操作できることを確認した。
- 任意のコマンドの実行が可能で、Docker コンテナの起動も可能。
- しかし、AppArmer による制限があるので注意が必要。
また、主には Ubuntu で試したのだけど、SSM エージェントは Raspbian にもインストール可能となっている。任意のシェルコマンド実行までは動作することを確認した。Docker コンテナ起動については未実施。
参考
- Manage Raspberry Pi devices using AWS Systems Manager | AWS Management Tools Blog
- Amazon EC2 Linux インスタンスに SSM エージェント を手動でインストールする - AWS Systems Manager
- Linux ハイブリッド環境のサーバーおよび VM に SSM エージェント をインストールする - AWS Systems Manager
- ruby on rails - Docker Containers can not be stopped or removed - permission denied Error - Stack Overflow
- Amazon EC2 Systems ManagerがRaspbian OSに対応したのでRaspberry Piにインストールしてみた | DevelopersIO
Gitbook の環境を Dockerで作った
しばらくきちんとしたドキュメント作成から遠ざかっていたのだけど、仕様書なるものを書く事になった。
基本的に文章を書くのは Markdown 形式に寄せていくようにしているので Gitbook を使うことにした。最終的に PDF にすることも簡単であろうと。
しかし、PDF 出力でハマった。その記録。
要件
目的はシステムの仕様書を書くということを踏まえ次の要件で環境を作ることにした。
実現方法の検討
1. Markdownで記述できること
「1. Markdown で記述できること」は Gitbook を採用した時点で満たせる。
最初から Gitbook を使うつもりだったので、検討するまでもない。
ただ、ドキュメントの量が大きくなってくるとビルドに時間がかかりそうなのが気がかりだったので代替プロダクトを一応探してみた。
mdBookという Rust 製の Gitbook クローンを見つけたのだけど、mdBook 自体も言語から発展途上感が強かったので、今回は見送った。
で、当初の方針通り Gitbook を採用して進めることとした。
2. UMLも記載したい
これについては Gitbook のプラグインで解決できそうなので、どれを使うかということで探した。
mermaidを利用したものがお手軽で良さそうだったのだけど、クラス図をサポートできるものが見つからなかったので PlantUMLを利用したもので検討した。
PlantUML を利用するプラグインはいくつもある。ググってすぐ出てきたのは、plantumlなのだけど、それを改良したっぽい umlを使って見ることにした。
動作はするのだけど、gitbook serve
で使っていると、UML の出力が遅くて快適に使えない。PlantUML で UML をレンダリングする際に Java でできた PlantUML を動かすために JVM の起動から始まるのが遅い原因だと思われる。
そこで、PlantUML サーバをつかって UML をレンダリングするタイプのplantuml-cloudを使ってみることにした。狙い通り UML のレンダリングは速くなった。 速度改善と同時に PlantUML に必要な graphvizのインストールも不要になった。 オフライン時も使えるようにしたいのでローカルで PlantUML のサーバである plantuml-serviceを動かすことにした。Docker イメージも提供されているので、次の Docker コンテナを動かす Docker Compose ファイルを作った。
- plantuml-service
- nginx
- Giitbook
nginx は plantuml-service をリバースプロキシするために入れている。というのも plantuml-service の待受ポート番号(1608)と plantuml-cloud の接続ポート番号(80) が一致しておらず、変更もできなかったのだ。それで仕方なくリバプロして Gitbook からのリクエストを受けられるようにした。
docker-compose.yml は次の通り。
version: "2.2" services: plantuml: image: 'bitjourney/plantuml-service:1.3.3' nginx: image: nginx:alpine volumes: - ./nginx/default.conf:/etc/nginx/conf.d/default.conf depends_on: - plantuml gitbook: image: hero/docker-gitbook:latest volumes: - ./gitbook:/gitbook - ./dest:/gitbook_dest ports: - 4000:4000 depends_on: - nginx command: 'serve'
2018/11/08追記 plantuml-cloudに送っていたプルリクがマージされ、ポート番号が指定できるようになった。これに伴い nginx によるリバプロは不要になり、次の docker-compose.yml のように plantuml と gitbook だけでよくなった。
version: "2.2" services: plantuml: image: 'bitjourney/plantuml-service:1.3.3' gitbook: image: hero/docker-gitbook:latest volumes: - ./gitbook:/gitbook - ./dest:/gitbook_dest ports: - 4000:4000 depends_on: - plantuml command: 'serve'
この環境でplantuml-cloudを使うには次のように book.json で plantuml-cloud を設定する。
{ "plugins": ["plantuml-cloud"], "pluginsConfig": { "plantuml-cloud": { "protocol": "http", "host": "plantuml", "port": 1608 } } }
3. PDFで出力できること
Gitbook の PDF 出力は calibreに依存している。 calibre をインストールしている他の Docker イメージを参考にしながら次のような Dcokerfile を作った。
FROM node:10.12.0-alpine LABEL maintainer="HeRoMo" ENV GLIBC_VERSION 2.28-r0 RUN apk add --update curl && \ curl -Lo /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub && \ curl -Lo glibc.apk "https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VERSION}/glibc-${GLIBC_VERSION}.apk" && \ curl -Lo glibc-bin.apk "https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VERSION}/glibc-bin-${GLIBC_VERSION}.apk" && \ apk add glibc-bin.apk glibc.apk && \ /usr/glibc-compat/sbin/ldconfig /lib /usr/glibc-compat/lib && \ echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \ apk del curl && \ rm -rf glibc.apk glibc-bin.apk /var/cache/apk/* ENV LD_LIBRARY_PATH $LD_LIBRARY_PATH:/opt/calibre/lib ENV PATH $PATH:/opt/calibre/bin ENV CALIBRE_INSTALLER_SOURCE_CODE_URL https://raw.githubusercontent.com/kovidgoyal/calibre/master/setup/linux-installer.py RUN apk update && \ apk add --no-cache --upgrade \ bash \ ca-certificates \ gcc \ mesa-gl \ python \ qt5-qtbase-x11 \ wget \ xdg-utils \ libxcomposite \ xz && \ wget -O- ${CALIBRE_INSTALLER_SOURCE_CODE_URL} | python -c "import sys; main=lambda:sys.stderr.write('Download failed\n'); exec(sys.stdin.read()); main(install_dir='/opt', isolated=True)" && \ rm -rf /tmp/calibre-installer-cache RUN yarn global add gitbook-cli svgexport COPY ./start.sh /usr/bin/ RUN chmod 755 /usr/bin/start.sh ENTRYPOINT ["start.sh"]
さて、このイメージを使って PDF を作って見ると、PDF はできるにはできるが、UML の図が表示できない。
plantuml-service では SVG で UML をレンダリングしているので、PNG ならどうだろうかと試そうにも plantuml-service は SVG のみサポートなので使えない。PDF 作成時のみローカルで PlantUML を動作するようにして試してみたがダメだった。
イメージを小さくしようと alpine ベースのイメージをベースにしていたが、そのかわり calibre やそれが依存している glibc を自前でビルドするようにしていたらそこに何らかの不足があるようだ。
試しに Debian ベースのイメージをベースに作り直した。それでは calibre もパッケージマネージャでインストールできる。UML を含む PDF の出力も問題なく出力できる。
最終的に gitbook の Dockerfile は次のようになった。
FROM node:10.13.0-slim LABEL maintainer="HeRoMo" # install apt packages RUN apt-get update -y && \ apt-get install -y \ bzip2 \ calibre && \ apt-get autoremove -y && \ apt-get clean && \ rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/* RUN yarn global add gitbook-cli svgexport COPY ./start.sh /usr/bin/ RUN chmod 755 /usr/bin/start.sh ENTRYPOINT ["start.sh"]
シンプルになった。イメージのサイズは約 900MB と少々大きくなったけど。
リポジトリは次の通り。
Dockerhub はこちら。
GAS を Typescript でリファクタした
以前、GAS を Webpack と Babel でモダンに実装した話を書いた。
このエントリを要約すると次の通り。
- Togglで記録した作業時間をRedmineの作業時間に登録する Google スプレドッドシートアドオンを作った。
- google\/clasp を利用して、ローカルでコーディング、バージョン管理した。
- ES6 で JS を書いて webpack + babel で GAS 用にビルドするようにした。
Claspが Typescriptをサポートした
まあまあモダンな JS で GAS を実装できる構成を確立したことで、今後はやりやすくなるぞと思ってから、半年ほど。
Clasp に驚くアップデートがあった。v1.5.0 で Typescript をサポートしたのだ。
clasp push
コマンドを実行すると、JavaScript のコードを対応する GAS プロジェクトファイルにアップロードしてくれるのだが、Typescript のコードも自動的にコンパイルしてアップロードしてくれるようになったのだ。
もちろん、HTTP 通信には UrlFetchApp.fetch
を使わないといけないとか GAS プラットフォームの制約はあるものの、コードのシンタックスは Typescript のものが使える。
つまり、次が使える。
型以外は webpack+babel の構成でも実現していたので、それほど目新しさはないが、webpack も babel も必要なく、これまで通りのコマンドを使うだけでトランスパイルしてアップロードしてくれる。
Toggl2rm を移行してみた
これは便利そうだということで、以前作ったToggl2rmを移行してみた。
移行前後のコードは次の通り。
依存モジュールが激減
移行前はこんな数のモジュールに依存していた。
- @google/clasp
- babel-core
- babel-loader
- babel-preset-env
- babel-preset-gas
- eslint
- eslint-config-airbnb-base
- eslint-plugin-googleappsscript
- eslint-plugin-import
- file-loader
- gas-webpack-plugin
- html-webpack-plugin
- webpack
それが、移行後はたったの 3 つ。
トランスパイルに必要な一切を @google/clasp
が担保してくれるので、
どんどんアップデートされる webpack や babel を追いかける必要もない。
その他必要なモジュールの組み合わせにも悩まされることもない。
@types/google-apps-script
によって GAS 固有のクラス群にも型チェックできるようになるし、VSCode ならばコード補完が使えるのでドキュメントを確認する回数も減るだろう。
いいことしかない。
コードの書き換え
もともとのコードが ES6 だったこともありコードの書き換えも少なかった。
- 拡張子を
*.ts
に変更する - 各関数の引数と戻り値に型アノテーションを追加
- tslint のエラー箇所を修正
- 変数
global
を削除
ほぼこれだけで移行完了だった。
結構、関数コメントに引数や戻り値について書き込んでいたけれど、やはりコードで定義できるほうが確実だと思った。 そして、コード補完が感動的に便利。
いいことしかない。
まとめ
clasp で Typescript を使うのは簡単。
趣味レベルで使っている GAS のコードは作った後はそれほど頻繁にはいじらないと思う。そういうコードほど型アノテーションは恩恵があると思う。 また、ビルドの依存モジュールも少なくてコード以外のもののメンテナンスが最小化される。
GAS の実装で Typescript を使わない理由が見当たらない。