なんかかきたい

プログラミングなどの個人的なメモやサークル「ゆきいろパラソル」の情報を載せてます

nginxでupstreamをhttpsにしたいとき

ちょっとだけ知って置く必要がある設定があるので残しておく。あまり難しくはない。

証明書を更新しておく

CentOSならyumDebianならapt、その他はいい感じに。

yum install ca-certificate

nginx をビルドする

がんばってください。

SSLを使うことになるので、http_ssl_moduleは入れておく必要がある。

opensslをリンクする場合、新しいものを使うのがおすすめ。

nginx.confを設定する

クライアント証明書が必要な場合は、ちゃんとした証明書と検証用にtrustedな証明書を設定する。

nginx.conf

server {
  resolver     8.8.8.8;
  listen       8080;
  server_name  _;

  proxy_ssl_protocols TLSv1.2;
  proxy_ssl_ciphers   HIGH:!aNULL:!MD5;
  proxy_ssl_server_name on;
  proxy_ssl_name REPLACE_REMOTE_ADDR;

  proxy_ssl_password_file       /path/to/client_cert_password;
  proxy_ssl_certificate         /path/to/client_cert_pem_with_password;
  proxy_ssl_certificate_key     /path/to/client_cert_pem_with_password;
  proxy_ssl_trusted_certificate /path/to/ca-bundle.crt;

  proxy_set_header Host               $host;
  proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Host   $host;
  proxy_set_header X-Forwarded-Server $host;
  proxy_set_header X-Real-IP          $remote_addr;

  location / {
    proxy_pass https://REPLACE_REMOTE_ADDR;
  }
}

各設定値について

resolver

外に行く場合の名前解決に使うDNS。IP指定の場合はいらないかもしれない。適切なDNSを指定する。

listen

nginxがlistenするポート

server_name

_ なので何でもマッチする。default_serverにするといい気もする。

proxy_ssl_protocols

proxyで接続する際に使うプロトコル。TLS1.2をサポートしているサーバの場合はこの設定でいい。

proxy_ssl_ciphers

proxyで接続する際に使うcipher。リモート側でサポートしているもので強いものを使う。

proxy_ssl_server_name

SNI対応用。proxy_ssl_nameとセットで使う。

proxy_ssl_name

REPLACE_REMOTE_ADDR を実際に接続する先の名前に置き換える。

リモートサーバにつなぎに行くときの名前を置く。SNIを使っているサーバがぼちぼちあるので指定するのが無難。

proxy_ssl_password_file

プロキシに使うSSL証明書のパスワードファイル。

クライアント証明書がパスワード付きならここにパスワードファイルを設定する。

proxy_ssl_certificate

クライアント証明書を指定する。クライアント証明書が不要ならいらない気もする。

proxy_ssl_certificate_key

クライアント証明書を指定する。クライアント証明書が不要ならいらない気もする。

proxy_ssl_trusted_certificate

証明書の検証に使う信頼された証明書を指定する。

CentOSだと /etc/pki/tls/certs/ とかに置いてあるらしい。

ディストリビューションによって違うのでいい感じに設定するか、curlのをもってきてもいい。

proxy_set_header

いい感じに設定する

終わり

この辺触る人あんまりいなさそうなので地味なハマり方をして消耗する人はいそう。頑張って欲しい。

カスタムして作った CentOS7.4 をAWSの M5, C5 で動かすときに落ちた沼について書いておく

今回もそこそこの不幸があった。普通にセットアップする分には簡単な案件だけど、ちょっと面倒なオプションがついて、カスタムしてダレカの作ったAMIをいい感じに新しいタイプのインスタンスで動かしたいというのがきた。やれやれ。

前知識

新しい世代のEC2インスタンスタイプ M5/C5 が東京リージョンで利用可能になりました

NITRO世代(C5、M5)へのEC2インスタンスタイプ変更を試してみた(Amazon Linux編)

  • https://dev.classmethod.jp/cloud/aws/change-type-to-m5-nitro-generation/
    • 元々 M5 タイプに対応していないAMIをアップグレードで対応する場合のことがちょっとだけ書いてある。
    • 記事を読むと、ENAを有効にし、NVMeドライバが使えるようにすれば起動するように見える。
    • AmazonLinuxの例だが、CentOSでも同じようにできるはず。

EC2で稼働中のCentOS 7を新しいインスタンス M5/C5 で起動させる準備をする

  • http://info.m-up.com/news/?category=staffdiary&id=2000001333
    • もうちょっと泥臭い感じの内容
    • AWSのドキュメント通りにコマンドを流してだいたいうまく起動できた、と書いてある
    • 後半kernelを4系にあげてあるが、単に動かしたいだけなら3系でも問題はない
      • 新しいkernelを使うメリットは例えばnvmeドライバの改善、まあ気にしなくてもいいかもしれない

VPC 内の Linux インスタンスにおける Elastic Network Adapter (ENA) を使用した拡張ネットワーキングの有効化

起動しないM5インスタンス

ena supportのオプションはインスタンスで有効にした状態でスナップショットを取るとAMIにも引き継がれ、AMIから起動したEC2インスタンスもENAが有効になります。

OSの起動前に問題が発生した場合、システムログの出力がないため調査が難しいのですが、インスタンスを停止するとシステムログの出力が得られることがあるので、起動してしばらくしてもSSHができない場合は、インスタンスを停止してシステムログを確認する。

今回の場合、 dracut-initqueue timeout で停止しており、 switch_root でEBSに切り替えようとしているところでパーティションが見つけられずエラーになっていた。

ログを見た感じ、疑われたポイントは、fstab、grub、initramfs、nvmeドライバ。

EC2 HVMの仕組み

仮想化方式(HVM と PV)についてまとめ

HVMインスタンスの仕組みは、物理マシンに近く、ルートデバイスにあるブートローダーからkernelを起動する。裏側はXenだけど。

なのでシステムログをよく見ると

  • ブートローダ (grub) から grub2 を起動
  • /boot/grub2/grub.conf を読んで、 kernel vmlinuzinitrd initramfs.img みたいなことが行われて
  • initramfsから起動した小さなOSがルートデバイス/ へ switch_root
  • / からOSを起動

という流れになっているのがわかる。

dracut-initqueue timeout は initramfs のところで起きているのでその辺りを調べていく。

dracut

RHEL系のディストリビューションで使われているinitramfs生成ツール。

結論を書くと、dracutの設定にnvmeドライバの追記をしていなかったため、/を見つけられず起動に失敗していただけだった。

dracutにドライバを含めるには、 /etc/dracut.conf.d 以下にファイルを置く。例えば /etc/dracut.conf.d/nvme.conf として以下のファイルを作成する。

add_drivers+=" nvme "

その後、 dracut を実行する。

dracut -v -f

これで initramfs が再生成され、中に nvme ドライバが含まれるようになる。確認したい場合は、cpioとかを使って生成されたinitramfsを展開すれば確認できる。

終わりに

疲れた。勘弁してくれ。

まあ、この辺は以前にinitramfsとかブートローダとかnvmeドライバとかその辺を調べてあったから比較的早く解決できた気はする。

どっちかというとPXEBOOT周りを触ってる際に覚えたことだけど、事前知識がなかったらこの問題は相当難しい問題だったと思う。難of難。

AWS触るときにもこの辺りの知識が必要になるってのは意外だけどそんなもんかな。いや、普通にやってたらハマらないしこの辺触らないんだよな...

zabbixについてまとめておく

Zabbixは総合監視ツールでmuninのようなメトリクスグラフを出したり、nagiosのような死活監視を行う機能をまとめて持っている便利ツール。

prometheus + grafana のように高速ではないが、長く使われているためそれなりの情報がある。

反面、古いといえば古臭さはあり、WebGUIでポチポチやる必要があるところが今ひとつだったり、agentで送れるデータが1つだったりする。

また、好みはあるが、個人的にはグラフはイマイチでメトリクスグラフはmuninのほうが簡単で綺麗という感想です。

一応WebAPIがあるので、自動的に設定を作ることはできなくはなさそうだけど、何回も設定するようなものではないし、 自前で作るのは正直面倒ではあります。

あと、zabbix-agentは監視項目が多くなるとホストマシンのCPUを消費する傾向があるので、多すぎる監視項目に気をつける必要がある。 この辺りは最適化テクニックもあるらしいけど、必要な監視のみに削る方が楽。

Zabbixは非常に高機能なので色々なことができるみたいですが、とりあえず使い始めるに当たって必要なところだけまとめる

zabbix-web

ZabbixサーバのWebインターフェイス。 Zabbixは設定をMySQLなどのDBに持つが、Webインターフェイスから設定を変える場合に使う。

管理 > ユーザ

Zabbixはユーザごとにプロファイルを持ち、Webのビューや障害通知のメールなどを設定できる。

デフォルトで Admin ユーザが設定されているが、複数人で扱う場合にはそれぞれ適切にユーザ登録を行うのがよい。

ユーザには種類があり、「Zabbixユーザー」と「Zabbix特権管理者」がある。 一般ユーザは「Zabbixユーザー」になるが、「設定」や「管理」が行えず、ホスト登録や障害通知のトリガー設定など多くの 運用に必要な設定ができないため、運用者は「Zabbix特権管理者」の権限を使うのがよい。

単に閲覧だけできればよいのであれば「Zabbixユーザー」でもよい。 グループごとに閲覧できる範囲を限定したり、大規模な会社向けの機能っぽいものもあるが割愛する。

設定 > アクション

アクションはZabbixが何かしらの状態変化を検出した場合に何かの動作を自動的に行うというもので、その名前の通りの機能。

何かしらのイベントには、「トリガー」、「ディスカバリ」、「自動登録」、「内部イベント」がある。

トリガーというのは、ホストから取得している数値に変化があった場合に通知として使えるもので、 いわゆるホスト監視目的で使える。障害復旧通知とか。

ディスカバリと自動登録はZabbixがホストを見つけた場合に使うのもので、例えば名前を自動で埋めたり監視項目を自動設定したりできる。 ディスカバリと自動登録は、受け攻めの違い程度なのでどちらも似たような感じで設定する。

内部イベントはその他色々なイベントを実行の条件にできるようだが、特に使っていないのでわからない。

とりあえずの設定で言えばトリガーを設定できれば死活監視の仕組みを作れる。

デフォルトの通知は「Report problems to Zabbix administrators」で「Zabbix administrators via すべてのメディア」となっている。

「Zabbix administrators」というのはデフォルトのユーザグループで、 通知を受け取りたいユーザは「Zabbix administrators」に所属させておけばよいと思う。

「すべてのメディア」とあるが、メディアというのはユーザごとに設定できる通知の方法で、「メール」「SMS」「スクリプト」など任意に設定できる。

Slackのようなチャットツールに通知したい場合は、自前でスクリプトを設定すると簡単にできる。 設定方法はユーザプロフィールの右上のアイコン。

設定 > テンプレート

テンプレートというのは、複数のホストに対してまとめて設定をいれるために使う設定のセットのようなもの。

Zabbixのマニュアルだと、「アイテム」、「トリガー」、「グラフ」、「アプリケーション」、「スクリーン」、「ローレベルディスカバリ」などが 例になっている。

とりあえずの設定で言えば、まずは「アイテム」と「トリガー」を設定できれば十分。

「アイテム」とはホストから取得するデータのことで、具体的には「CPU使用率」や「メモリ使用量」、「起動プロセス数」など。 監視項目と考えてもいいかもしれない。取得するデータは組み込みのもののほか任意のスクリプトから取得することもできる。

「トリガー」はテンプレートの単位で「アイテム」で取得した数値が一定の値を取った時にアクションを行う設定。 具体的には、「アイテム:CPU使用率が50%を超えた場合」といった条件をトリガー起動の条件として設定する。 トリガーの設定にはアイテムの設定が必須となる。

アイテムやトリガーはテンプレートではなくホストごとにも設定できるが設定管理の面からテンプレートを使うのがベター。

設定 > ホストグループ

テンプレートには必ずホストグループを設定する必要があるが、設定してもテンプレートの内容がホストグループに適用されるわけではない。

先の説明通りになるが、テンプレートはホストごとに設定するもので、この設定は対象ホストの選択時に絞り込めるだけにすぎない。

実際にホストにテンプレートを適用するにはリンクをする必要があり、これはホスト側からもテンプレート側からも行える。

ホストグループはホストをまとめる論理単位だが、テンプレートには直接影響しないため注意。イマイチ慣れない機能。

とりあえず

死活監視はこれだけで十分作れる感じはある。長く使われているだけあって、情報は多いし悪くはない。

グラフは好みあるけどZabbix単体だとホストグラフを横断的にみることができないからキツいかなあ。

まあとりあえずいい感じのツールなのは間違いないです。

長い付き合いだったNagiosさんもIcingaになりそうで古いプラグインが使えなくなるとかなんとかだそうで この手の監視ツールも大変だなーという感想です。

AWS CodeDeployについて書いておく

CodeDeployはAWSが提供しているアプリケーションコードデプロイの仕組みで、各サーバに特権で動くagentをあらかじめインストールしておき、CodeDeployにデプロイ指示を出すと、agentがコードをダウンロードし、サーバ上で任意のコードを実行することで、アプリケーションのデプロイをするというもの。

これだと、単なるプル型デプロイの仕組みのように見えるが、実際には少しだけ高機能なところもあって、例えばAutoScalingGroupに対してまとめてデプロイしたり、EC2インスタンスに結びつけたタグを元にデプロイ対象を選択することもできる。

一番便利なところはおそらくロードバランサーとの強調動作で、ELBのようなロードバランサーから一時的に隔離しながらデプロイをすると綺麗にデプロイができる。

さらにはインスタンスごと切り替えるいわゆるBlue/Greenデプロイの仕組みも用意されているので、Javaのアプリケーションのように再起動を避けられないプログラムでも比較的安全な自動デプロイの仕組みを比較的簡単に用意できる。   とはいえ、この辺りはAWSの機能にベッタリなため仮にAWSから引き上げる時には足枷になるような気はする。 同じような仕組みを用意するのはやや骨が折れる。 まあ、AWSの場合はCodeDeployだけの問題ではなく、ELBやアクセス権限周りの話もあるのでどのみち引き上げる時は面倒か。   あと、EC2が提供しているイメージ以外は自前でagentをインストールする必要があるが、agentはruby製なのでシステムにRubyを入れる必要はある。

まあ、この点はRubyの場合アプリケーションコードを動かす目的ではよくrbenvが使われるため、システムワイドにインストールしたRubyの影響を受けることはほぼないと考えていいと思う。   特権で動く点も気になるという人もいるとは思うが、アプリケーションコードのデプロイ作業ではなんだかんだで特権が必要になるケースはある。

実際に動かすアプリケーションコードは権限を落として実行すればいい。 この辺りはsystemdになって書きやすくなったと思う。悪くない。  

CodeDeploy Agentのインストール

  CodeDeployの場合、特に公式ドキュメントの日本語がややおかしい気もするけど自動翻訳とか使ってる可能性もあるなーという感想です。   気が向いたらgithubに公開されているドキュメントにコントリビュートするといいかもしれない。(これは一般的な話なんですが、AWSでもよく使われる場所のドキュメントは整備されているので、裏返せばCodeDeployはあまり使われていないのかもしれないですね)   * Amazon Linux または RHELAWS CodeDeploy エージェントをインストールまたは再インストールします。 * https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/codedeploy-agent-operations-install-linux.html  

CodeDeployにサービスロールをつける

  CodePipelineとかはこの辺り自動的に設定してくれたような気もしていたけど、CodeDeployだと自分で用意する必要があるっぽい。面倒だがそういうものなら仕方ない。   codedeploy.amazonaws.com を信頼したエンティティにして、CodeDeployに適切に権限を与える。  

EC2インスタンスにS3のアクセス権をIAM Roleで与える

  CodeDeployのagentでS3からコードを取ってくるようにしている場合、取ってくるためのS3バケットへのアクセス権が必要になる。

これの設定自体は大したものではないので、単にEC2にアクセス権を与えておけばいい。  

s3バケットのバージョニング

  コードのバージョン管理にS3のバージョニング機能を使うことができる。これはコードが動かない場合にrollbackをしたい場合に便利。  

CodeDeployにpush

  アプリケーションコードをCodeDeployにpushすると、デプロイタスクが作られagentがコードを取りにいく。

gitリポジトリだとデプロイ対象には含めたくない例えば .git などがいるので、デプロイ前のタスクで対象のファイルのみ別ディレクトリに書き出しておき、アーカイブするのが綺麗な気はする。

で、実際にはこのタイミングではWebだとassetのビルドとかがあるし、いわゆるビルドタスクなのかもしれない。まあ普通か。   デプロイについて気をつけないといけないのは、サーバ台数が最低でも2台必要になる点。

これは最低でも1台ごとにアプリケーションを切り替える都合、1台では一時的にダウンしてしまうため、CodeDeploy側で制限されているため。

検証環境みたいなところだと、この制限がやや邪魔だなあと思うこともあるが、検証環境へのデプロイは別の方法でやればいい。共通ができないじゃないか。やれやれ。   最低台数の話ついでに、デプロイをどの程度まとまった台数行うかどうかは設定で決めることができて、1台ずつ、半分、全部と選ぶことができる。

1台ずつは徐々に切り替わっていくので、LBから外れる台数も少なくデプロイ時の負荷に余裕はあるが時間がかかる。

半分は速度優先。LBから外れる台数も半分なので、負荷が高い時には厳しい選択かもしれない。

全部はなんだ、ワイルドだな!  

終わり

  まあ、使えるといえば使えるんだけど、Webコンソールは開発者にはあんまり親切じゃないかも。雰囲気だけど。どうすればいいかというのも難しいので仕方ないか。   とりあえず使えはするし、AWSの仕組みとは親和性はそこそこよいという感想だけど、AWS以外で使えないんだよなあ...いやそれは違うか。使えはするけど利点があんまりないというか。

プル型のデプロイツールについてはあんまり調べてないけど、自前で作るのはそこそこ手間だし、何かないかなという気持ちはありますね。

クラウドだと、インスタンスをポコポコ立てたくなるし、そういう時にデプロイ対象を増やすよりは勝手に拾ってくれる方が向いてる。   とはいえ、作るのは多少手間ではある。やれやれ。

AWS CodePipelineからCodeBuildを呼んでlambdaで古いAMIを消すといい感じになる

前回の続き

t-cyrill.hatenablog.jp

AWSでAMIの作成をCodeBuildを使って自動化したけど、CodeBuildが動く部分も自動化したいので、Githubに置いてあるリポジトリのmasterにマージされたタイミングで自動的にAMIが作られるといい感じなんじゃないかと思って、「これくらい簡単にできるっしょ」ってやろうとしたらCodeBuildのトリガーなんてものはないと気づいてAWSめんどいってなったので、めんどい部分を書いておきます。

これ作ってから言うのもアレなんですが、CodePipelineはAWSコンソールから操作する分にはある程度自動化されていて便利な反面、 自前でAMI周りの権限をうまくつけるのは結構面倒なので、terraformとかでCodePipeline/CodeBuild/Lambda/CloudWatchLogs/IAM/S3まわりを一気に設定するのは骨が折れるなあという感想です。

まあ、やろうと思えばできます。ただただ面倒です。なんとかならんのか。

で、本題なんですが、CodePipelineっていうのはCIツール的なやつで、CodeBuildがビルド周りしか担当しないのに対して、CodePipelineはソースの取得、ビルドタスクの実行、ビルド後のタスクなどなど、パイプラインの名の通り色々なタスクをルールに従って処理してくれるやつです。

ビルドの並列化などもできるので、CodeBuildだけで足りないところをいい感じに補ってくれるやつと捉えておくといいかもしれません。

CodePipelineのソースにgithubリポジトリを指定して、masterブランチを監視しておけば、Pull Request単位でレビューをしてmasterにマージされたタイミングで自動的にCodeBuildを走らせるなんてことが簡単にできます。

便利ですね。

流れとしては

  • githubのブランチを監視、変更があればソースで取り込む
  • CodeBuildでAMIを生成
  • Lambdaで古いAMIを削除

という感じになります。

CodePipeline

CodePipeline自体はUIをみたらなんとなく使い方はわかるくらい簡単なので、特に説明はしないのですが、いくつか気になったところだけ補足します。

ソースはアーティファクトの出力が必要

ソースフェーズで取り込んだgithubリポジトリをCodeBuildに渡す際にアーティファクトという形でs3を経由する必要があります。

まあ、ソースなんでそのソースはs3に置くでしょという感じな気もするのですが、一時的にs3に置かれるのはちょっと面倒だなあという感想もあります。

なんか、噂によるとパーミッション周りが消えるとかいう話もあるし、ソースじゃなくて単なるトリガーみたいなのをつくれないのかなあという気持ちはあります。

ここはあんまり気にせずいきましょう。必要な権限があればCodeBuildのprebuildとかでつければいいかなという感想です。

タイムアウト時間には余裕を持っておく

CodeBuildもLambdaもですが、意外にも時間がかかることがあるのでタイムアウト時間が短すぎるとエラー扱いになります。

CodeBuildは初回実行時におおよその時間を割り出しておくと良いでしょう。LambdaはAMI削除程度なら15秒もあれば十分かなという程度です。(実際には2秒程度で終わる)

LambdaからCodePipelineのタスクが成功したかどうかは自分で報告する必要がある

地味にハマった問題で、タスク呼び出しで呼んだLambdaが成功したかどうかはLambdaでCodePipelineに通知する必要があります。

そうしないと、たとえLambdaが正常終了したとしてもタイムアウトまで待った上で失敗扱いになるので、見栄え上よくないです。

これはテンプレート的に成功の場合は通知処理を書いておけばまあ問題にはならないでしょう。

Lambdaで古いAMIを削除する

まあ、消しましょう。Jenkinsのように古いビルドとアーティファクトを消すものはないので自前で作る必要があります。面倒ですね。

Lambdaなので、Pythonでサクッと実行します。itemgetter あたりが知らなかったので勉強になりましたね。

AWS AMI AutoCleanImages

images_limit とかは適当にいじってやるといいと思います。ec2のAPIを呼ぶのでこのLambdaにはEC2の操作権限をつけないといけません。

あと、上で書いたCodepipelineへの通知は code_pipeline.put_job_success_result(jobId=event['CodePipeline.job']['id']) のところになります。

もうなんかとりあえず最後にこれを置いておくかという感じですね。

終わりに

とりあえず、AMI作成はこれで自動化できるようになりました。

あとは本投入周りのことは考える必要がありますが、まあその辺がインフラエンジニアの腕の見せどころでしょう。

正直この辺はハンコなのでインフラエンジニアは隠居していたいところなのですが、まあサービスしておきましょう。

はー、こんなことより早くアプリケーション側から呼ばれているMySQLのめっちゃ遅いクエリ消さないとなー!!!

あ、書くの忘れてたんですが、terraformだと今のところCodeBuildの環境変数設定でParameter Storeを指定することができないので、CodeBuildのタスク内で秘密情報はSSMから取ってくる必要があります。

まあそのうち改善しそうですが、具体的にはansible-vaultのパスワードみたいなのは自分で取ってくる必要があるという感じです。頑張って下さい。

最近暇すぎてGolangを勉強がてら書いてます。

本当はGoroutineを使った並列処理とかそういうのを書きたいんですが、ベンチマーカーくらいしかそんな用途なさそう。

だいたい速度面で困ってないし。

次回はこの辺書くかなという感じです。世界を笑顔に。

AWS CodeBuild と packer と ansible でプロビジョニング済みのAMIを自動生成する

AMI生成部分を自動化できたので、ちょっとだけまとめておく。

CodeBuild

  • AWSのDockerで動くCIツール。
  • CodeBuildの名前の通り、ソースコードをビルドする環境を簡単に作れることをウリにしている
  • CircleCIなどのサービスでビルドしたり、Jenkinsを自前で用意するのと比較してそれほど違いはない
    • 利点があるとすれば、AWS上で動くためサービスロールを使って権限を付与できるところか
    • packerを使う都合上、EC2へのアクセスが必要になるため、外部サービスの場合アクセストークンの発行と管理が必要になる
  • 料金はビルド時間ごとの課金
    • 一応無料枠もある

Packer

  • HashiCorp製のマシンイメージ自動生成ツール
  • Goで書かれていて機能が豊富
    • AWS以外にもいくつかのクラウドサービスに対応している

Ansible

  • Python製のプロビジョニングツール
  • シェルスクリプトでもいいが、モジュールが整備されているので使えるようになると便利
  • Packerと連携するとSSH周りを自動で設定してくれる

参考ソース

AWSのブログにCodeBuildとPackerを使ったAMIビルダーの構築方法がある。

他にはクラスメソッドさんのブログにAnsibleとPackerの連携周りがある。

この辺りを読めばなんとなく作り方のイメージはできるようになるが、いくつかハマった点があるので補足としてまとめておく。

このリンクだけで十分な人はここから先は読まなくてもOKです。

大まかな流れ

  • Ansibleのplaybookはgithubで管理しておく
    • ここは変えてもよいが、CodeBuildはgithubと連携できるのでgithubを使うと簡単。
    • CodeBuildでコードを取ってくる方法は、他にS3やGithub Enterprise、BitbuckerとCodeCommitがある
    • 最悪の場合でもS3が使えるのでなんとかなるとは思う。
  • CodeBuildでビルド用のDockerを起動、事前のセットアップを行う
    • buildspec.ymlprebuild に書く
    • DockerでAnsibleとPackerを実行するのでインストールする
      • インストール済みのDockerイメージを用意できればわざわざビルドのたびにセットアップする必要は無くなる
    • Dockerイメージは任意のものを使って構わないが、AWSが提供しているのは現状 Ubuntu 14.04 LTS (Trusty) ちょっと古いか。
  • ansible-vaultで使うパスワードをKMSから取ってくる
    • 平文を嫌う場合、KMSに登録しておき、環境変数にセットする方法が使える
  • packer build でAMIを生成する

AWSのブログと違う点

  • Packer 1.2.1
  • ansible provisionerを使うため、Docker環境へAnsibleのインストールする
  • packerがIAM Roleを扱えるようになったため、クレデンシャルの生成は不要になった
  • 環境変数 USER を設定する
    • 設定しないと packer user: Current not implemented on linux/amd64 が出る
    • これはDocker環境では環境変数 USER が設定されていないため。Golanguser を取るところでエラーになる
    • CodeBuildの環境変数設定でやっておけば問題はない

クラスメソッドさんのPacker+AnsibleによるAMIの作成と違う点

  • アクセスキーはIAM Roleを使うので変数を使わない
  • ansible-localではなくansibleを使う
  • ansibleのインストールは buildspec.yml で行う

クラスメソッドさんのPacker 0.9の新機能 リモートからのAnsible Provisionerが追加されましたと違う点

  • vaultファイルの管理をする

本題

CodeBuildとPackerを使うため、それぞれのための設定ファイルを用意する。

CodeBuildが buildspec.yml で Packer は任意の名前のJSON

buildspec.yml

---
version: 0.2

phases:
  pre_build:
    commands:
      - echo 'Install Packer 1.2.1'
      - curl -qL -o packer.zip https://releases.hashicorp.com/packer/1.2.1/packer_1.2.1_linux_amd64.zip && unzip packer.zip
      - echo 'Install Ansible'
      - apt-get update -y
      - apt-get install -y software-properties-common
      - apt-add-repository ppa:ansible/ansible
      - apt-get update -y
      - apt-get install -y ansible
      - echo 'Validate packer json'
      - ./packer validate packer_ec2.json
  build:
    commands:
      - aws configure set region $AWS_REGION
      - echo "$ansible_vault_pass" > vault_password_file
      - echo 'Build AMI'
      - ./packer build packer_ec2.json
  post_build:
    commands:
      - echo "Build finished `date`"

packer_ec2.json

{
    "variables":{
        "vault_path": "vault_password_file"
    },
    "builders": [{
        "type": "amazon-ebs",
        "region": "ap-northeast-1",
        "source_ami": "ami-XXXXXX",
        "instance_type": "t2.micro",
        "ssh_username": "root",
        "ssh_timeout": "5m",
        "ami_name": "packer_{{ timestamp }}"
    }],
    "provisioners": [{
        "type" : "ansible",
        "extra_arguments": "--vault-password-file={{user `vault_path`}}",
        "playbook_file" : "playbook.yml",
        "groups" : ["aws", "webserver"]
    }]
}

groups を配列で渡すと inventory file で複数のグループに属した場合と同じ扱いになる。

これは group_vars などで設定を切り替える必要がある場合に便利。

vaultファイルは buildspec.yml で出力したパスを extra_arguments を使ってそのまま渡している。

IAM Role

  • XXXXXXXX にはアカウントIDを入れる

  • 以下のポリシーを作成し、CodeBuildで使うIAM Roleにアタッチする。(インラインでも構わない)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Action": [
                "ssm:GetParameters"
            ],
            "Resource": [
                "arn:aws:ssm:ap-northeast-1:XXXXXXXX:parameter/ansible-vault-pass"
            ]
        },
        {
            "Sid": "",
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt"
            ],
            "Resource": [
                "arn:aws:kms:ap-northeast-1:XXXXXXXX:key/alias/aws/ss"
            ]
        }
    ]
}

KMSからデータを取ってくるために必要。名前は適宜設定すること。

終わりに

いくつかハマりポイントはあったが、これでAMIをCodeBuildで自動的に作れるようになった。

あとはgithubのMergeにHookするとかすれば自動でAMIがどんどん作られていくので便利。

ただ、古いのを消さないと溜まり続けて課金がすごいことになりそう。大変ですね。

現場からは以上です

AWSの管理をterraformとroadworkerにした

Ansibleのcloud_moduleで大量に書かれたplaybookをterraformに少しずつ移して、 Route53の管理だけをroadworkerに移してみたのですが、 今のところうまくいっているようなので紹介しておきます。

terraform

HashiCorpのGoで書かれたオーケストレーションツール。

各OS、ディストリビューションに合わせた実行可能ファイルが配布されているのでインストールはとても簡単。 インストール方法で悩むチームでも導入の敷居は低いだろうと思う。というかよろしく入れた。

同種のツールとしてはcloudformationがあるが、terraformはGCPなどにも対応している。

設定ファイルはクラウド側の事情に引っ張られやすいため、EC2の設定をGCPに使うということはもちろんできないが、 管理方法などは共通化できる面もある。

ドキュメントはかなりまとまっていてやりたいことに対して設定の例が乗っていることが多い。 多少学習コストはあるが比較的低いと言える。

設定ファイル

terraformの設定ファイルは *.tfディレクトリに任意の名前でおいて良い。

terraformの特徴の一つがこの設定ファイルの読み込み方法で、ディレクトリ内のすべての *.tf が読み込まれて評価される。

具体的に言えば、 s3.tfrds.tf のようにファイルを分割しても 「s3.tf のみ適用する」ということはできない。

触ったことがないと戸惑うポイントになるが、このルールは「ちゃんと設定を書けば、結果的に個別の適用をする必要は無くなる」ためむしろ楽になる。

terraformの設定はHCLで書く。HCLとは「HashiCorp Configration language」である。

まあ名前の通りなので、特に気にせずこういう感じで書くとだけ思えばいいと思う。

YAMLとかJSONとかそういうものではないが nginx の設定などにインスパイアされた、 人にもそこそこ読みやすく機械的にも扱える設定フォーマットを目指していることもあって、 慣れればそれなりに書きやすい、と思う。

data "aws_ami" "ubuntu" {
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  owners = ["099720109477"] # Canonical
}

resource "aws_instance" "web" {
  ami           = "${data.aws_ami.ubuntu.id}"
  instance_type = "t2.micro"

  tags {
    Name = "HelloWorld"
  }
}

上記の設定はEC2のインスタンスを立ち上げる設定の例でterraformのマニュアルのもの。

amiinstance_type など AWS 固有の設定があることがわかると思う。

特徴的なのは属性値の参照、 ${data.aws_ami.ubuntu.id} の部分で、 data "aws_ami" "ubuntu" でフィルターして得られたAMIのidを使うという意味になっている。

terraformは設定ファイルに他の設定によって決定する値を埋め込むのが得意なため、 ひとまとまりの構成を比較的簡単に記述することができる。

これは各設定が依存するということでもあるが、記述量が大幅に減らせ変更にも強くなる。

裏返すと、変更が困難な箇所を変更する際に多くの箇所で変更を強いられる場合もあるが、 terraformには実行計画を事前に出力する機能があるため、一応意図しない大幅な構成変更は避けることができるようになっている。

stateファイル

terraformのもう一つの特徴が状態管理で、terraform.tfstate という名前のファイルで実行時に状態を保存するようになっている。

このためterraform以外で(例えばマネジメントコンソール)リモート状態を変更すると手元の状態とリモートの状態が一致しなくなり問題が起こる場合がある。

基本的にterraformは1箇所で実行し terraform.tfstate はたまにバックアップを取っておくのがよさそうだが、 terraform.tfstate にはパスワードなどのセンシティブな情報が含まれることがあるので、gitで管理するのは避けたい。

中身はただのテキストなので、なんとなく手で直すこともできるし、壊れた場合は、importを使ってリモートの状態を tfstate に取り込む方法もある。

terraform import はドキュメントに細かい使い方が乗っているが、あくまでのリモート状態を手元に同期するだけで、 *.tf ファイルを作ることはできないので注意。ただし、適当に作って terraform import した後に差分がないように設定を自分で書くことはできる。

roadworker

わざわざterraformを使っているのにRoute53の管理はterraformを使わないのかという話になるんですが、 前述の通り、terraformには構成ファイル *.tf を生成する機能はない。

これは既存の設定を取り込む必要がある場合に非常に面倒で、DNSのレコードが大量に存在する現場では厳しい。というかまさにこれ。

roadworkerのいいところは、設定ファイルを自動生成する機能がある点で、これだけでも使う価値がある。

生成時にzoneごとにファイルを分けることもできる。まあ、ゾーン内にrrsetが多すぎるとどのみち長くはなるが。

欠点はRubyで書かれているため、Rubyを導入できるだけの現場じゃないと導入が難しいところ。まあ、ここはゴリ押した。

あと、Ruby 2.4対応が完了していないため、Ruby 2.3で動かすのをオススメ。テスト機能で大量に警告が出る。 Rubyユーザとしては最新版で動かないのが少しモニョるが、rbenvとbundlerを使うなら環境は固定できるので、普段使いする分には困らないだろう。

終わりに

まあ当然といえば当然なんですが、用途に合わせて適切なツールは導入した方がいいですね。

学習コストの問題はありますが、ansibleにオーケストレーションを頑張らせるのは却って大変な気はします。

まあ、aws系モジュールのタスクが、boto/boto3を使ってAWS APIをそのまま呼び出していそうなところがplaybookを書くだけで見えてくる時点でお察しな感じはします。

あと、changedとかの判定も今ひとつだし、何より事前に実行計画を見ることができないのは痛いです。terraform plan 相当のことが、ansible-playbook -C では達成できません。

もちろんプロビジョニングについては ansible は強力なツールです。うまく使い分けましょうというお話でした。(これを理解してもらうのがなかなか難しい

現場からは以上です。