ジョギング
山王公園を 30 分くらい, 懸垂 x 6 回
ランチ
近所のラーメン屋さん. 博多ラーメン. スープが美味しかった.
ダラダラ
ランチの後, 出川哲朗が充電して回る番組を見ながら昼寝してしまった...その後はダラダラと過ごしてしまった.
夕飯
久しぶりに頑張ってパスタを作った. オイルが多すぎて奥さんには不評だったので, 残飯処理的に自分でほとんど食べた.
タイトルの通りです.
ChatOps という言葉がインターネット上に流布されるようになったのは, 古くは 2003 年!!, そしてグーッと盛り上がってきたのが 2013 年!!ということなので, 今更感がありますが YAMAP にて EC2 の運用に ChatOps を取り入れようとしているので, どんなことをやっているのかをメモっておきます.
YAMAP 社内では EC2 インスタンスでいくつか社内サービスを提供していますが, その中でも, エンジニアチームが検証の為に自由に利用する環境が複数台 EC2 で稼働しています. 今回はこの検証環境用 EC2 (OS は Amazon Linux) を ChatOps で運用するようにしてみた話です.
検証環境用 EC2 には以下のような課題がありました & あります. (現在進行系の課題もあります)
そして, 検証したい人が「検証したいだけなのに, サーバーのことを気にするのが辛い」というようなハードルを下げたいのと, 何より, 「俺が楽したい」という思いがあります.
ということで, これらの課題をどのように解決したのか, 解決しようとしているのかをざっくりとで恐縮ですがダンプしていきたいと思います.
以下のような感じでやっています.
YAMAP は業務に Slack を利用しており, 業務インフラには無くてはならない存在になっています. そして, Slack には Bot (Hubot) が飼われていて, 社内の福利厚生であるシャッフルランチや掃除当番の抽選から Github と連携し CircleCI によるテストやデプロイの指示役等大活躍しております. この bot を仕込んで, 社内文化の一つとして作り上げてくれたのは @morygonzalez さんには本当に感謝です. ちなみに bot の名前は tengu
氏と呼びます.
ということで, 既に bot 文化は根付いていることから, 少なくとも, エンジニアチームのメンバーは bot を操作することに対するアレルギーは無いという前提で色々と進めたいなーと考えています. 気軽に bot を呼び出して欲しいなあと.
これは YAMAP にジョインした時点で既に自動起動, 自動停止の仕組みは構築されていました.
まず, 以下のように tengu
氏に呼びかけると, 指定した EC2 インスタンスが起動します. 停止も同様です. 当然, 以下のコマンドを叩くことで, 任意のタイミングでの起動や停止が可能になっています.
# tag:Name kensho01 を起動する tengu ec2 start kensho01 # tag:Name kensho01 を停止する tengu ec2 stop kensho01
自動起動や自動停止は, このコマンドを別の bot (cronbot
氏) に叩かせています. この cronbot
氏は, Slack の slash コマンド経由で呼び出すことで, 通常の crontab と同じ感覚で利用することが可能です.
# crontab -l と同等 /cron list # 月曜日から金曜日までの 9 時に tengu ec2 start kensho01 を実行 /cron add 0 9 * * 1-5 tengu ec2 start kensho01
cronbot
氏は, 以下の OSS を利用させて頂いています.
ありがとうございます.
自動起動, 自動停止だけであれば, AWS Lambda を利用するという選択肢がありますが, 任意のタイミングでの起動や停止, 自動起動, 停止の時間をカジュアルに変更し辛いので, Slack のコマンドでちゃちゃっと出来る点は素晴らしいなあと思っています.
前述の通り, 検証環境用 EC2 はエンジニアであれば誰 (エンジニアに限ってはいませんが) でも利用が可能です. ただ, 誰が利用しているかは, Slack のエンジニアチームチャンネルに問い合わせる必要がありました.
@here kensho01 を使いたいんですが, どなたか使っていますかー?
これでは非効率なので, EC2 のタグに利用者を登録する仕組みを追加しました. まずは, 誰 ( or どのチーム) が利用しているのかを確認する為に, 以下のように tengu
氏に呼びかけます.
tengu feature reservations?
下図のように出力されます.
Reserved By
が空欄になっている為, 誰にも利用されていないということにしています. ここで, インスタンス kensho01
を利用したい場合には, 以下のように呼びかけます.
tengu kensho reserve 01 kappa
下図のように出力されます.
利用状態を解除したい場合には, 以下のように呼びかけます.
tengu kensho release 01
これらの仕組みは, 先述の通り, EC2 タグを利用していて, reserve
を呼ぶと EC2 タグ reserved_by
に第四引数 (上記の例だと kappa
) を登録しているだけです. 同様に release
を呼ぶと EC2 タグ reserved_by
の値をクリアしています.
ここから少し込み入った話になります.
検証環境用 EC2 上では素の Docker エンジンが動いていて, 各種サービスは docker-compose で管理されたコンテナで起動しています. これらのコンテナが起動しているか, また, コンテナ上のサービスが利用可能であるかインスタンスにログインせずに確認出来れば嬉しいのではと考えました. また, 確認だけでなく, コンテナの起動や停止も行えれば, 更に利便性も上げられそうです.
ということで, これらの要件を AWS Systems Manager を利用して実装してみました.
AWS Systems Manager とは, EC2 インスタンスだけではなく, オンプレミスサーバーや他の AWS リソースの設定を管理する機能を集めたもので, その中から AWS Systems Manager Run Command (以後, Run Command) を利用しました.
Run Command は簡単に言うと, EC2 やオンプレミスサーバーにインストールされている SSM エージェントを介して, ドキュメントと呼ばれる, 予め用意された (用意している) コマンドを実行してくれるサービスです.
検証環境用 EC2 インスタンスには, docker-compose up -d
やコンテナ上で稼働するサービスの動作を確認する為のコマンドをまとめたスクリプトを用意しておきます. EC2 インスタンス再起動時に実行出来るように, init スクリプトとして用意しました.
#!/bin/sh # chkconfig: 2345 99 10 # description: control containers for kensho environment USERNAME=xxxxxx function start_containers() { /sbin/runuser -l ${USERNAME} -s /bin/bash -c " eval $(aws ecr get-login --no-include-email --region ap-northeast-1) docker-compose pull docker-compose stop docker-compose up -d " } function get_kensho_service_status() { ... 略 ... } case "$1" in start) start_containers ;; stop) stop_containers stop_ssmweb ;; status) get_kensho_service_status ;; *) break ;; esac
EC2 インスタンス上では, 以下のように実行されることを想定しています.
$ sudo service kensho-service status ===== Certificate Status notBefore=Jun 4 02:46:05 2019 GMT notAfter=Sep 2 02:46:05 2019 GMT ===== Containers Status NAMES CREATED STATUS IMAGE container1 4 minutes ago Up 4 minutes image1 container2 4 minutes ago Up 4 minutes image2 conteiner3 4 minutes ago Up 4 minutes image3 ===== API Status HTTP/1.1 200 OK ===== Web Status HTTP/1.1 200 OK ===== Batch Status HTTP/1.1 200 OK
start
では, コンテナの起動 docker-compose -d
や, サービスを動かす為の各種初期設定が実行されます. stop
では, 単純に docker-compose down
が実行されます.
上記の init スクリプトを Run Command で実行します. 以下のようなシンプルなドキュメントを用意しました.
{ "parameters": { "action": { "type": "String", "description": "(Required) Specify a action(start|stop|status).", "displayType": "textarea" } }, "schemaVersion": "2.2", "description": "update kensho environment", "mainSteps": [ { "action": "aws:runShellScript", "name": "control_kensho_env", "inputs": { "runCommand": [ "service kensho-env {{ action }}" ] } } ] }
あとは, hubot から Run Command でコマンドを実行する為の API を叩くだけ (厳密に言うと, IAM ポリシーを付与したりする必要はあります) です. API を叩く部分の実装は以下のような感じです.
// 略 .... const sendCommand = async (name, action) => { const targetInstance = await detectInstance(name) let result try { result = await ssm.sendCommand({ DocumentName: 'control_kensho_env', InstanceIds: [targetInstance.InstanceId], Parameters: { 'action': [ action ]} }).promise() } catch(e) { result = e } return result } const checkIfOperationFinished = async (name, commandId, channel) => { const targetInstance = await detectInstance(name); let message; let count = 0; while(true){ result = await ssm.listCommandInvocations({ CommandId: commandId, InstanceId: targetInstance.InstanceId, Details: true }).promise() if (result.CommandInvocations.length != 0 || count > 20){ if (result.CommandInvocations[0].StatusDetails != 'InProgress') { break; } } count++; sleep.sleep(3); } message = result.CommandInvocations[0].CommandPlugins[0].Output; robot.messageRoom(channel, "コマンドの実行結果です.\n```\n" + message + "\n```") } // 略 ....
見よう見まねの実装なので, すごく汚いかもしれませんが, sendCommand
してから listCommandInvocations
をループでコールしてコマンドの終了を待ったりする部分は苦労しました.
以下のように tengu
氏に問い合わせます.
# kensho01 のサービス稼働状況を取得します tengu kensho service status 01 # kensho01 のサービスを停止します tengu kensho service stop 01 # kensho01 のサービスを起動します tengu kensho service start 01
以下, 実際に tengu kensho service status 01
を実行している様子です.
start
も stop
もこんな感じの出力になります.
EC2 の起動や停止, サービスの起動, 停止や稼働確認はやりたいことが出来た気がしています. 次のステップとして, 必要に応じて, 検証環境用 EC2 インスタンスを chatops で手軽に増やしていけるような仕組みを考えたいと思います.
これは EC2 じゃなくても良いかなと. ECS だとクラスタを増やしていけば良いのかな. ECS や EKS 等, 既にベンダからは様々なソリューションが提供されています. これらをうまく組み合わせて, よりエンジニアの皆さんの利便性が上がるように, そして自分が楽になれるような仕組みを考えていければなあと考えています.
ということで, YAMAP に入社してから半年が経とうとしていますが, こんな感じのことを色々とやっていますので, 今後共よろしくお願いいたします.
冒頭に書きましたが, chatops という言葉が夜に放たれてから 10 年以上の時を経て, 今更感は否めませんが, 業務効率向上の為, そして, 何よりもインフラ担当者が少しでも楽になるような仕組みはドンドコ導入していきたいし, 手を動かして行ければ (手を動かさなくても回るのがベスト) なあと考えています.
寝坊.
もくもく会, 始まってます〜. 青柳さんの近況話で盛り上がっています! #jawsug #jawsugfuk
— Yohei Kawahara(かっぱ) (@inokara) June 6, 2019
青柳さんの近況話から, 今日, 初参加の方との話等, AWS の話をちゃんとたくさん出来た気がする. こういうのを積み重ねると良いような気がする.
奥さん特製のハンバーグ. 今日は, 若干ゆるい目玉焼きをのっけて美味しかった!
山王公園 35 分. 懸垂を 6 回. 引き続き, 左足かかと付近に違和感あり.
奥さんにお弁当を作ってもらった. ポテトサラダサンドイッチ. とても美味しかった. ポテトサラダはもちろんだが, パンもしっとりしていて最高だった.
YAMAP にて開催.
今日の #fukuokarb はゴールデンウィークの自由研究で作った, 社内の蔵書管理 bot (Ruby 製) を良くする作業をしたいと思います.
— Yohei Kawahara(かっぱ) (@inokara) 2019年6月5日
自分の進捗は芳しくなかったけど, mruby が動く M5Stack というマイコンの話で盛り上がって楽しかった.
奥さん特製のハンバーグ. 美味しかった!!!が, やっぱり, 出来たてよりもジューシー感がイマイチだった.
山王公園 35 分. 懸垂を 6 回. 昨日よりは楽にはしることが出来たけど, 左足かかと付近に違和感あり.
いろいろと上手くいかない. 少し, 気分転換が必要だなと思ったり.
知見を得た. ちょっとまとめたいと思う.
山王公園 30 分くらい (時計を押すの忘れた). 懸垂を 6 回. めっちゃキツイし, 足がなぜかパンパン.
バタバタしてた.
文章書く力が無さすぎるので小学生の方がもっとまともなことを書くかもしれないけど.
最近, 一番近い身近で起きていたことから, 改めて感じたこと等を稚拙な文章になってしまい恥ずかしい限りですが, ダラダラと書いていきます.
あくまでも個人的な感覚になってしまいますが, 仕事でもプライベートでも, 以下のような状況においては絶対に証跡は残すことが本当に大事. どんなことがあっても証跡を残すこと.
証跡が全く無い場合, あったとしても相互に共有されていない場合, 然るべき場所に駆け込んだところで, 「証拠がないとねえ」って言われて, 半ば門前払いになります. たとえ, 話を聴いてくれたとしても, 「これ以上楯突いてもお金と時間の無駄ですよ〜」と暗に諭されるはずです.
今回, 自分の身近で起きたのは, 当方には手書きレベルのメモがあり, それを元に代理の方が先方に確認したところ, その内容はでっち上げ扱いされた挙げ句に, 先方の記録によって代理の方が諭されてしまうという事態が発生して笑ってしまいました. わかってはいましたが, 証跡を残すことだけではなく, それは双方で共有すべき. これを改めて痛感しました.
誰かの為に動くという事は悪いことではありません. 困っている人が居れば積極的に手を差し伸べるべきだと教えられてきました.
ただ, その差し伸べた手をいつまでも差し出し続けるのか, 適当な時期を見計らって手を引くのか, その判断はとても難しいことだと思います. だからこそ, 誰かに手を差し伸べる時には, そこを見据えて動くべき (そこまで責任を取るつもりで動くべき) だと思います. 想定しない圧力や, 状況の変化によって, 急に手を引くくらいであれば, 最初から手を差し伸べるべきではない (関わるべきではない) と思いますし, 自分自身も, 誰かにそういう態度を取ってしまっていないか猛省しました.
急に手を引かれるとどうなるか. 「梯子を外される」という言葉の意味とは違いますが, マンガ的にはまさに, 途中まで登った壁から急にはしごを外されて, 登り切るか, 飛び降りるかの選択肢しか無くなって困っている人が壁に張り付いたままどうしようもない状態が目に浮かびます.
今回, 当事者は壁を登りきったと思います. 梯子無しで登りきりました. ただ, 登りきった先には, さっきまで梯子を持っていてくれた人が何事も無かったように立っているだけした. なぜ, 梯子を外したのかは誰もわかりません.
人間不信のその先に, 自分は何を思えば良いのかわからなくなりました.
現在, 働いている会社 (YAMAP) では, EC2 インスタンス (OS は Amazon Linux がメイン) がまだ数台動いています. これらのインスタンスのメモリとディスクの使用率を CloudWatch のカスタムメトリクスに送りつけて監視を行いたかったのでツールを作りました. そして, そのツールを CircleCI でビルドして, さらに RPM パッケージ化出来るようにしたので, その概要と苦労した点をメモります.
尚, Datadog 等の SaaS ツールの方がええやんという思いはありましたが, これらの EC2 は退役を控えているシステムであり, あまりコストはかけず, お手軽に監視を追加する方法を考えた結果自作ツールとなりました. 技術的な負債としては小さいものだと考えています.
まだまだベータ版なので, 以下のような課題を抱えています.
メモリ使用率, ディスク使用率を監視するツールは勉強を兼ねて, 先人達の知恵を借りつつ Go で実装してみました. コードについては, 近日中に追加したいと思いますが, 以下のような機能を提供しています.
尚, 実装にあたり, 以下のリポジトリのコードを参考にさせて頂き, Go ルーチンを利用した実装にしてみました. とても参考になりましたありがとうございました.
イチからメモリ監視の実装を書くのは辛い (というか, 無理です) ので, 以下のパッケージを利用しました.
/proc/meminfo
以下の情報をパースして構造体として返してくれるので, 以下のように直感的にメモリの使用量等を取得出来ます.
memory, err := memory.Get() if err != nil { fmt.Fprintf(os.Stderr, "%s\n", err) return } if *argDebug { fmt.Println("---------- debug print ----------") fmt.Printf("memory total: %d bytes\n", memory.Total) fmt.Printf("memory used: %d bytes\n", memory.Used) fmt.Printf("memory cached: %d bytes\n", memory.Cached) fmt.Printf("memory free: %d bytes\n", memory.Free) }
最高です.
先述の通り, syscall パッケージを利用しています. 以下のような短いコードで指定したパスの使用量等を取得しています.
func DiskUsage(path string) (disk DiskStatus) { fs := syscall.Statfs_t{} err := syscall.Statfs(path, &fs) if err != nil { return } disk.Total = fs.Blocks * uint64(fs.Bsize) disk.Free = fs.Bfree * uint64(fs.Bsize) disk.Used = disk.Total - disk.Free return }
また, メモリ使用率と同じような感じで取得出来るようにしました.
disk := DiskUsage(*argPath) if *argDebug { fmt.Println("---------- debug print ----------") fmt.Printf("disk total: %d bytes\n", disk.Total) fmt.Printf("disk used: %d bytes\n", disk.Used) }
RPM パッケージを生成するにあたり, 以下のパッケージが必要になります.
yum install rpmdevtools yum-utils
また, 肝になるのは SPEC ディレクトリ以下に設置する以下のような spec ファイルです.
%define _binaries_in_noarch_packages_terminate_build 0 Summary: EC2 Monitoring Toolset for disk Usage Name: ec2-monitoring-toolset-disk Version: 0.0.3 Release: 1 License: MIT Group: Applications/System URL: https://github.com/xxxxxxxx/ec2-monitor-tools Source0: %{name} Source1: %{name}.initd Source2: %{name}.logrotate BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root Requires: logrotate %description %{summary} %prep %build %install %{__rm} -rf %{buildroot} %{__install} -Dp -m0755 %{SOURCE0} %{buildroot}/usr/local/bin/%{name} %{__install} -Dp -m0755 %{SOURCE1} %{buildroot}/%{_initrddir}/%{name} %{__install} -Dp -m0644 %{SOURCE2} %{buildroot}%{_sysconfdir}/logrotate.d/%{name} %clean %{__rm} -rf %{buildroot} %post /sbin/chkconfig --add %{name} /sbin/chkconfig --level 3 %{name} on %files %defattr(-,root,root) /usr/local/bin/%{name} %{_initrddir}/%{name} %config(noreplace) %{_sysconfdir}/logrotate.d/%{name}
個々の設定内容については言及しませんが, init スクリプト等の関連するスクリプトの設置までこのファイルでやれるということを知りました. 上記のファイルでは,
Source0: %{name} Source1: %{name}.initd Source2: %{name}.logrotate BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root ... 略 %install %{__rm} -rf %{buildroot} %{__install} -Dp -m0755 %{SOURCE0} %{buildroot}/usr/local/bin/%{name} %{__install} -Dp -m0755 %{SOURCE1} %{buildroot}/%{_initrddir}/%{name} %{__install} -Dp -m0644 %{SOURCE2} %{buildroot}%{_sysconfdir}/logrotate.d/%{name}
このあたりです. また, 配布したいツール自身 (今回だと, Go でビルドしたバイナリファイル) は SOURCES
ディレクトリに放り込んでおけば良いことも知りました. ですので, Go でビルドしたバイナリではなくても, シェルスクリプト等も同じように RPM パッケージとして配布することが出来ます. 社内等の組織の中でバッチ処理等のスクリプトを配布する場合, 配布の手法を統一するという意味では RPM パッケージ化するというのもありかもしれません.
Workflows を使っています. 以下のようなシンプルなワークフローです.
build
ジョブでは go test
(取って付けたようなテスト) が行われた後, RPM パッケージを生成しますtest
ジョブですdeploy
ジョブは RPM パッケージを S3 バケットにアップロードするステップを想定しています今回の作業で得た知見は, 最初の build
ジョブで生成された RPM パッケージを次の test
ジョブや deploy
ジョブに共有したい場合どうするか. .circleci/config.yml
の以下の部分で指定しています.
jobs: build: docker: - image: amazonlinux:1 working_directory: /root/go/src/toolset steps: ... 略 ... - persist_to_workspace: root: /root/rpmbuild/RPMS/x86_64 paths: - ./* test: docker: - image: amazonlinux:1 steps: - attach_workspace: at: /root/rpmbuild/RPMS/x86_64 - checkout ... 略 ... deploy: docker: - image: amazonlinux:1 steps: - attach_workspace: at: /root/rpmbuild/RPMS/x86_64 ... 略 ... workflows: build-test-deploy: jobs: - build - test: requires: - build - deploy: requires: - test
build
ジョブの steps
の persist_to_workspace
キーで共有したいパスを指定して, 次のジョブの steps
で attach_workspace.at
で共有されたパスを指定すれば良いようです. 詳細についは, 以下のドキュメントに記載されています.
メモでした. ちゃんと RPM パッケージを作ったことが無かったのでとても勉強になりましたし, Go をちゃんと勉強しなければと思った次第です.