tl;dr
現在, 働いている会社 (YAMAP) では, EC2 インスタンス (OS は Amazon Linux がメイン) がまだ数台動いています. これらのインスタンスのメモリとディスクの使用率を CloudWatch のカスタムメトリクスに送りつけて監視を行いたかったのでツールを作りました. そして, そのツールを CircleCI でビルドして, さらに RPM パッケージ化出来るようにしたので, その概要と苦労した点をメモります.
尚, Datadog 等の SaaS ツールの方がええやんという思いはありましたが, これらの EC2 は退役を控えているシステムであり, あまりコストはかけず, お手軽に監視を追加する方法を考えた結果自作ツールとなりました. 技術的な負債としては小さいものだと考えています.
まだまだベータ版
まだまだベータ版なので, 以下のような課題を抱えています.
- RPM バッケージをどこにアップロードするか検討中... 多分, S3 に置くけど, 安全に RPM リポジトリとして利用する方法を調査中
- RPM パッケージのインストール手順の作成 (itamae とか Ansbile とか)
監視ツールについて
メモリ使用率, ディスク使用率を監視するツールは勉強を兼ねて, 先人達の知恵を借りつつ Go で実装してみました. コードについては, 近日中に追加したいと思いますが, 以下のような機能を提供しています.
- メモリ監視は /proc/meminfo から情報を取得する
- ディスクに関しては, syscall パッケージ を利用して情報を取得する
- 取得した情報を計算して CloudWatch のカスタムメトリクスとして登録する
尚, 実装にあたり, 以下のリポジトリのコードを参考にさせて頂き, 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 パッケージ
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 パッケージ化するというのもありかもしれません.
CircleCI
Workflows を使っています. 以下のようなシンプルなワークフローです.
build
ジョブではgo test
(取って付けたようなテスト) が行われた後, RPM パッケージを生成します- 生成された RPM パッケージを実際に Amazon Linux コンテナにインストールして, 意図した通りにインストールされているか等をテストするのが
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 をちゃんと勉強しなければと思った次第です.