ecs_deployのecs_auto_scalerにPRを出すときに環境構築をがんばったよ、というお話
先日、 ecs_auto_scaler
にPRを出してマージされてしばらく運用して問題なさそうので今日はちょっと自慢をします。
ecs_auto_scaler
そのものについては前回の記事でふれたのでそちらをご参照ください。
複数のサービスが連携させて動かすためのgemですが、こういったgemの場合、動作確認のための環境構築がなかなか大変です。
ecs_autoscaler
の場合、ロールやセキュリティグループを除いて最低限でも
- ecs_autoscalerでコンテナインスタンス数、タスク数を管理する対象はクラスタ単位ですが、管理される側のクラスタA
- ecs_autoscalerを動かすホスト
- upscale triggerとdownscale triggerのためのCloudWatchのアラート2種
あたりが必要です。毎回1から用意するのがめんどくさいので、先日仕事で動作確認をした時、ちょっと時間はかかりましたがTerraformで立てれるようにしました。
ので、今日はそれを自慢して終わりにします。どやぁ。
なお、セキュリティグループは検証用にインバウンドはHTTP, HTTPS, SSHのIPを全て空け、アウトバウンドは全部空け、と空きまくりなので検証が終わったらすぐに落としてください。現場からは以上です。
関連エントリ
ecs_deployによるECSのオートスケーリングとAWSのECSによるオートスケーリングの違いについて
今日は会社で管理しているOSSの一つ、ecs_deployに関連する話をECSの復習がてらします。
TL;DR
ecs_deploy
gemでは、ECSへのデプロイに関するCapistranoタスクの定義とECSのオートスケーリングを行うスクリプトが入っている- ECSのオートスケーリングはAutoScaling Groupのdesiredの設定でクラスタ内のコンテナインスタンスの数を、ECSのサービスのdesired countの設定でタスクの数を調整する
reproio/ecs_deploy のgemについて
ecs_deploy
の gem は、各言語で作成されているECSへのデプロイを助けるスクリプト群*1の一つで、特徴としてはECSのデプロイをCapistranoのタスクとして記述させるアプローチを取っていることだと思います。
ecs_deploy
のgemの中身はおおまかにいって
- ECSへのデプロイに関するCapistranoタスクの定義
- 上記Capistranoのタスクの中で利用されるRubyのコード
があって、今回自分が仕事で触ったのは ecs_auto_scalerr
の方なので、 ecs_auto_scaler
についてもう少し説明していきます。
また、ややこしいので、この記事ではAWSが提供しているECSのService AutoScalingを「AWSによるECSのオートスケーリング」、ecs_deployに含まれるecs_auto_saclerによるオートスケーリングは 「ecs_auto_scaler のオートスケーリング」と記載することにします。
ECSのオートスケーリングのために、ECS ServiceとAutoScaling Groupの設定をいじる必要がある
ecs_auto_scaler
はひらたくいうと、ECSのAutoScalingを行うスクリプトです。
このスクリプトが書かれたのは2016年1月には、まだAWSによるECSのオートスケーリングがありませんでした*2が、この二種類のオートスケーリングが行なっていることをおおまかにまとめると
ということをやっています。
Amazon Web Services ブログ > Amazon ECSでAuto Scalingによると、AWSによるECSのオートスケーリングの場合、
- ECSのサービスのタスク数の調整には、ECS ServiceのScaling Policy
- ECSのクラスターのコンテナ数の調整には、コンテナインスタンスが属するAutoScaling GroupのScaling Policy
を用いています。一方、ecs_auto_scaler のオートスケーリングは
しています。
何が言いたいかというと、2つのオートスケーリングで利用している設定やAPIに多少違いはありますが、ECSのオートスケーリングは、ECSのサービスとクラスターのAutoScaling Groupの2つのレイヤーの設定を管理して行う必要があるわけです。
AWSによるECSのオートスケーリングとecs_auto_scaler のオートスケーリングの違い
それでは、この2つのオートスケーリングの方法の違いで一体どういう事態が生じるのでしょうか。
じつは、AWSによるECSのオートスケーリングでは、ECS ServiceのScaling PolicyとAutoScaling GroupのScaling Policyがそれぞれ独立して動いていて、AutoScaling GroupのScaling Policyによりまだタスクが動作しているコンテナインスタンスが停止となりエラーが発生することがあります。
この問題に対応するため、 ecs_auto_scaler ではスケールイン時はECSインスタンス上のタスクの状態をしらべ必要なタスクが動いていないことを確認してから、AutoScaling Groupの設定の調整を行うようになっています*5。
AWSによるECSのオートスケーリングを使っている場合でもこの問題の対応は可能ですが*6、ecs_auto_scalerを利用するメリットとしては、ecs_deployの EcsDeploy::EcsAutoScaler
を利用する場合、ECSインスタンス上のプロセスのチェックを含めたオートスケーリングの処理を 管理対象クラスタの外部のホストで行う*7ため、監視される側のインスタンスには特別な設定をしなくていい点でしょうか。
そのかわり、オートスケーリングのためにネットワークを経由してAWSのAPIを叩くので、AWSのAPIの回数制限*8を超えるような規模のクラスタ、たとえば200台くらいのコンテナインスタンスが存在するような大規模なクラスタの管理は難しそうです*9。
書ける、と思ってたら割とかけなくて焦りました。。現場からは以上です。
*1:たとえば、pythonだと https://github.com/fabfuel/ecs-deploy , シェルスクリプト: https://github.com/silinternational/ecs-deploy/blob/develop/ecs-deploy, JSだと https://www.npmjs.com/package/ecs-deploy など
*2:https://aws.amazon.com/jp/blogs/news/automatic-scaling-with-amazon-ecs/ AWSによるECSのオートスケーリングがアナウンスされたのは2016年5月
*3:正確には少し違っていて、後発のターゲット追跡スケーリングポリシーはCloudWatchのアラートではなく、CloudWatchの特定のメトリクスを見て、その値が一定値に近づくようにする https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/service-autoscaling-targettracking.html
*4:MinやMaxなども増減させていますが、詳しくは https://dev.classmethod.jp/cloud/aws/comprehend-auto-scaling-desired-capacity/
*5:詳しくは https://github.com/reproio/ecs_deploy/blob/master/lib/ecs_deploy/auto_scaler.rb#L350-L356 あたり
*6:https://developers.cyberagent.co.jp/blog/archives/14664/
*7:ことが前提になっている、おそらく。
*8:ECSのAPIは1時間に1000回くらい叩くとエラーを返してくるようになるとかなんとか...
*9:なんとなく、200台超えてきたらインスタンスタイプ変えることの方を先に検討しそうな気もするけどAWS詳しくない...
ssh-agentでsshコマンドで認証鍵の指定を入れなくていいようにしたりログイン先のサーバに認証鍵の情報を転送したりする
TL;DR
ssh-agent bash
でssh-agent
を起動してssh-add /path/to/secret_key
することでssh
コマンドで認証鍵の指定やパスフレーズの入力が省略できるssh-agent
はOpenSSHの認証エージェントで、認証エージェントの「転送」により、ローカルで動いているクライアントが持っている認証情報をログイン先のサーバに転送することができるssh
コマンドの-A
オプションで「転送」を行う旨を指定する- 認証エージェントの転送を利用することで、たくさんの人がアクセスする可能性のあるサーバに秘密鍵をおかなくて済んだりする
SSHでパスワードを使わずにログインする
SSHでパスワードを使わずにログインするようにするには、
ssh-keygen
で秘密鍵と公開鍵のペアを作成する*1- ログイン先のサーバの
~/.ssh/authorized_keys
以下に公開鍵を設置して、所有者・グループ・権限を設定する*2 - 秘密鍵を
-i
オプションで指定してSSHコマンドを叩くssh -i /path/to/secret_key user@exapmle.com
という手順を踏みますが、さらに秘密鍵を指定しているオプションも ssh
コマンドに含まないようにしたいとします。
やり方は
SSH_CONFIG
*3にログイン先のホスト、ログインするユーザー名、そのユーザーがログインするときに使う秘密鍵のパスを指定するssh-agent
を起動して、ssh-agent
に認証に利用する鍵を追加する
といった方法があります。今日は ssh-agent
を使う方法についてもう少しメモします。
ssh-agentを利用してログインをする方法
ssh-agentは公開鍵認証を行うときに利用する秘密鍵の情報をメモリ上に保持するプログラム*4で、sshでログインを行う前に
$ ssh-agent bash
または
$ eval `ssh-agent`
で起動し、 ssh-agent -k
で停止します*5。
起動した後、
$ ssh-add /path/to/secret_key
# パスフレーズが指定された鍵であれば、このタイミングで入力する
でそのセッションのプロセスのメモリ上に秘密鍵の情報を登録し*6、 ssh-add -l
で登録した鍵の一覧を確認できます。
ssh-agent に鍵を登録した状態で ssh
コマンドを打つと、以下のように認証鍵の指定やパスワードの入力を行わずにリモートのサーバへログインすることができます。
$ ssh server.example.com
ssh-agentを利用してログインする場合にできること
さて、 ssh-agent
を使ってパスフレーズや認証鍵の指定なしでログインする方法を紹介しましたが、この手間はパスワード入力をなクスという意味では SSH_CONFIG
にログイン設定をする方法(具体的には
Host example.com HostName example.com IdentityFile ~/.ssh/id_rsa User woshidan
のように設定を書く)とたいして手間は変わりません。
それでは、 ssh-agent
を使うと SSH_CONFIG
を使う場合と比べてなにがちがうのでしょうか。
ssh-agent
は、OpenSSHにおける認証エージェントですが、認証エージェントには、認証エージェントを「転送」するという機能があります。
認証エージェントを「転送」するとはどういうことかというと、ローカルで動いてる認証エージェントが持つ認証鍵を含む情報をログイン先のサーバに通知し、ログイン先のサーバ上の sshd
デーモンにもローカルと同様の情報を持った認証エージェントとして機能してもらうことです。コマンドとしては
$ ssh -A [ユーザ名@]ホスト名
のように ssh
コマンドに -A
オプションをつけることで可能です。
このようにすることで、他のサーバへアクセスする必要のあるサーバに秘密鍵を配置する必要がなくなります。
たとえば、リモートのビルドサーバはビルドに必要なファイルを取得するために他のサーバにアクセスする必要がありますが、その際、秘密鍵をビルドサーバに配置すると、ビルドのデバッグのためにログインしたエンジニアやそれ以外の人(?)はそのファイルにアクセスできたりするわけですが。
管理者だけがアクセスできるようにしたサーバに秘密鍵を置き、そのサーバから ssh-agent
を使ってビルドサーバにログインしてビルド作業を行うようにすることで、秘密鍵にアクセス可能な人数を制限できたりします*7*8。
逆に、認証情報に関するエラーのデバッグのときは、 ssh
のオプションや ssh-add
が行われたタイミングも確認するとよさそうです。
現場からは以上です。
参考
- http://nigohiroki.hatenablog.com/entry/2013/08/18/221434
- https://euske.github.io/openssh-jman/ssh_config.html
- https://euske.github.io/openssh-jman/ssh-agent.html
- https://qiita.com/naoki_mochizuki/items/93ee2643a4c6ab0a20f5
- http://www.unixuser.org/~euske/doc/openssh/book/chap3.html
- http://www.unixuser.org/~euske/doc/openssh/book/chap4.html#make-life-easy-with-ssh-agent
- https://qiita.com/Yarimizu14/items/6a4bab703d67ea766ddc
*1:SSHでパスワードを使わずにログインする手順なので省いていますが、SSHでユーザがログインできるようにする作業全体としては、この前段階にSSHログインできるようにする目的で、ログイン先のサーバに adduser コマンドでユーザを作成し、passwdコマンドでパスワードを作成する必要がある refs: http://nigohiroki.hatenablog.com/entry/2013/08/18/221434
*2:詳細は http://nigohiroki.hatenablog.com/entry/2013/08/18/221434
*3:https://euske.github.io/openssh-jman/ssh_config.html だいたいユーザ用の設定ファイル ~/.ssh/config に書く
*4:https://euske.github.io/openssh-jman/ssh-agent.html
*5:この辺の便利なスクリプトは http://www.unixuser.org/~euske/doc/openssh/book/chap5.html#spread-ssh-agent にまとめっています。圧倒的感謝...!!
*6:他のオプションの利用例などは https://qiita.com/naoki_mochizuki/items/93ee2643a4c6ab0a20f5 が参考になる
*7:もっと、いろいろある気はしたんですが、自分が考えてぱっと出てきた例がこういうのが限界
*8:たとえば、Rundeckには秘密鍵などを管理するための画面があります https://rundeck.org/2.9.1/administration/key-storage.html https://komeda-shinji.blogspot.com/2015/02/rundeck_68.html
CloudWatchにさわってみたときのメモ(カスタムメトリクスのパブリッシュ、アラームの設定)
とりあえず今日はCloudWatchの復習で。
CloudWatchはAWSのモニタリングや監視のためのサービスで、EC2など各種AWSのサービスを動かすと自動的にCPU利用率などのメトリクスを取得してくれる他、AWS CLIなどを利用してカスタムメトリクスを送ることも可能です。
CloudWatchの用語: 名前空間とディメンジョン、メトリクス
それぞれのメトリクスには名前空間とディメンジョン、名前があります。AWSサービスの場合の名前空間はサービス名であることが多いです。
名前空間はそれぞれディメンジョンで区切ることができ、たとえばEC2名前空間に設定されているディメンジョンは
があります。(ディメンジョンが設定されていないデータについては
のように「範囲の定められていないメトリクス」に収められます。)
ディメンジョンをクリックすると出てくるCPU使用率などがメトリクスの名前にあたります。
カスタムメトリックを送信してみる
データのタイムスタンプと個別のデータポイント、あるいは、ある時刻のデータポイントの数、データの数値合計、平均といった集計値を送信することができます。 データのタイムスタンプは現在時刻から2週間前の間に指定する必要があります。
個別のデータポイントのデータを送る
TIMESTAMP=2018-08-08T08:08:00Z aws cloudwatch put-metric-data --metric-name RequestLatency --namespace GetStarted \ --timestamp $TIMESTAMP --value 8 --unit Milliseconds
ある時刻のデータポイントの数、データの数値合計、平均といった集計値を送信する
TIMESTAMP=2018-08-08T08:08:00Z aws cloudwatch put-metric-data --metric-name RequestLatency --namespace GetStarted \ --timestamp $TIMESTAMP --statistic-values Sum=40,Minimum=8,Maximum=8,SampleCount=8 --unit Milliseconds
送ったメトリクスは
のようにグラフで確認可能です。
CloudWatchのアラームを作成する
CloudWatchのグラフを表示している画面から、CloudWatchのアラームを作成することができます。
アラームは、アラームの状態ごとにAmazon SNSと連携させることでSlackやメールで通知、PagerDutyなどインシデント管理サービスのアイテム作成の自動化に利用できます。
アラームの状態変化について
アラームは基本的にアラーム作成画面の
の部分の条件式を満たしていたら OK
、そうでない場合は ALARM
, アラームの間隔内にデータポイントがない場合はINSUFFICIENT DATA
の状態になります。
の記事にあるコマンドを
$ aws cloudwatch set-alarm-state --alarm-name "RequestLatency is High" --state-value ALARM --state-reason "alarm-test"
のように打ち込むと一瞬指定した状態にアラームの状態を変化させることができます。その後、すぐに直近のデータに応じた状態に戻ってしまいますが、アラームの通知連携のテストにはむしろ便利でしょう。
アラームの状態に応じて動作するプログラムなどをテストするときは、欠落データの処理方法を変更するとデータを送っていない間の状態が指定しやすくて便利です。
参考
- https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/PublishMetrics.html
- https://qiita.com/kooohei/items/e9f8692b9ebe90b6a9df
- https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html
今日は短いですが、現場からは以上です。
EC2インスタンスをECSの特定のクラスタに所属させる
今日は、EC2インスタンスをECSの特定のクラスタのコンテナインスタンスとして登録するために必要なものについてまとめておきます。
EC2インスタンスがECSのコンテナインスタンスになる仕組み
ECSのコンテナインスタンスはecs-agent
が動作していて、ecs-agent
がECS APIを叩いてクラスタに登録したコンテナインスタンスなのですが、実は直接的にEC2インスタンスをECSのコンテナインスタンスとして登録するための設定はありません。
EC2インスタンスがECSのコンテナインスタンスとして動作するための条件を満たしていたら勝手にECSのクラスタにコンテナインスタンスとして登録される、という仕組みで初見は結構分かりにくかったです。。
EC2インスタンスがECSのコンテナインスタンスになるために必要なもの
- ECSに最適化されたAMIの利用
- AWSのアカウントに対する認証情報(どこの組織のECSインスタンスなのかわかる情報)が含まれ、ECSがコンテナインスタンスの登録やイメージの取得などに使うアクセス権限が付与されたIAMロール
- IAMロールをEC2インスタンスにアタッチするためのインスタンスプロファイル
- (EC2起動タイプを選択する場合のみ)SSHログインのためのキーペア
- (強く推奨)コンテナインスタンスを起動するVPC
ecs-agent
がECS APIと通信してコンテナインスタンスを登録するためのアウトバウンドの通信が許可されたセキュリティグループ- コンテナインスタンスを登録するクラスタを決定するためのuserdataで指定する環境変数 ECS_CLUSTERの指定
userdata
に指定がなければデフォルトのクラスタに登録する
それぞれ、Terraformで具体的に指定する様子を確認して今日はおしまいにしようと思います。
必要なものを作成するためのTerraformの設定
ECSに最適化されたAMI
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs-optimized_AMI.html などを参考に見繕う。
resource "aws_instance" "ecs_container_instance" { ami = "ami-e4657283" // 管理画面を操作しててでてきたのを使った気がする ...
IAMロール
このへんは昨日のエントリと同じですが。。
// 信頼ポリシーをロールに付与 resource "aws_iam_role" "ec2_instance_role" { name = "ec2_instance_role" assume_role_policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Action": "sts:AssumeRole", "Principal": { "Service": "ec2.amazonaws.com" }, "Effect": "Allow", "Sid": "" } ] } EOF }
// アクセス権限ポリシー resource "aws_iam_policy" "ecs_container_instance_policy" { name = "ecs_container_instance_policy" path = "/" policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ecs:CreateCluster", "ecs:DeregisterContainerInstance", "ecs:DiscoverPollEndpoint", "ecs:Poll", "ecs:RegisterContainerInstance", "ecs:StartTelemetrySession", "ecs:UpdateContainerInstancesState", "ecs:Submit*", "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "*" } ] } EOF }
// 信頼ポリシーが付与されているロールにさらにアクセス権限ポリシーを付与 resource "aws_iam_role_policy_attachment" "ecs_container_instance_policy_attachment" { role = "${aws_iam_role.ec2_instance_role.name}" policy_arn = "${aws_iam_policy.ecs_container_instance_policy.arn}" }
インスタンスプロファイル
// EC2インスタンスにはインスタンスプロファイルを利用してロールをアタッチ resource "aws_iam_instance_profile" "ecs_container_instance_profile" { name = "ecs_container_instance_profile" role = "${aws_iam_role.ec2_instance_role.name}" }
resource "aws_instance" "ecs_container_instance" { ... iam_instance_profile = "${aws_iam_instance_profile.ecs_container_instance_profile.name}" }
キーペア
// public_key_pathで指定したパスに ssh-keygen で作成した公開鍵があるとして resource "aws_key_pair" "auth" { key_name = "${var.key_name}" public_key = "${file(var.public_key_path)}" }
VPC
デフォルト*2のものを利用する場合、Terraformでの設定は不要。
セキュリティグループ
ECSインスタンスとして登録するだけなら、アウトバウンドの通信だけを許可すればよいです。実際はALBやAutoScalingGroupと組み合わせて利用するのでそれらのヘルスチェックや、連携して動く他のサービスからのリクエストを受け付けられるようにする必要がありますが。
resource "aws_security_group" "test_security_group" { name = "test_security_group" egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } }
userdataで指定する環境変数 ECS_CLUSTERの指定
#!/bin/bash echo ECS_CLUSTER=woshidan-test-cluster >> /etc/ecs/ecs.config
こういうスクリプトを
$ cat userdata.sh | openssl enc -e -base64
resource "aws_instance" "ecs_container_instance" { ... user_data = <<EOF IyEvYmluL2Jhc2gKZWNobyBFQ1NfQ0xVU1RFUj13b3NoaWRhbi10ZXN0LWNsdXN0 ZXIgPj4gL2V0Yy9lY3MvZWNzLmNvbmZpZw== EOF
のように指定します。
現場からは以上です。
参考
- https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/get-set-up-for-amazon-ecs.html
- https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/instance_IAM_role.html
- https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/launch_container_instance.html
- https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs-optimized_AMI.html
- https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs-agent-config.html
- キーペアの作成について http://kenzo0107.hatenablog.com/entry/2017/03/27/215941
IAMのロール、EC2インスタンスやECSのタスクに対するロールについてメモ
今日はIAMのロールについて復習しようと思います。
TL;DR
- 特別な作業をする時だけふだん使わない権限を一時的に使いたいときは、IAMロールの出番
- IAMロールの使い方
- IAMロールの使い所
- 一時的に強い権限をユーザーに付与する、外部のユーザーに組織内のリソースへアクセス権限を与える、EC2インスタンス上のアプリケーションやECSのタスクに権限を付与する etc.
- EC2インスタンス上のアプリケーションに権限を付与する場合はさらにIAMインスタンスプロファイルを用いてロールをインスタンスにアタッチする
- ECSのタスクに権限を付与する場合は、作成したロールのARNをタスク定義のパラメータ(タスクロール:
taskRoleArn
)に指定する
IAMのロールに関する用語や使い方の確認
IAMはざっくり言うと、権限を管理する単位としてIAMユーザーというアカウントを用意して、IAMユーザーやIAMユーザーが所属するIAMグループ*1にAWSのリソースへのアクセス権限を表すIAMポリシーをアタッチして権限を付与する、という感じのWebサービスです。
IAMユーザーというと聞きなれないですが、新しいチームに所属したときにAWSアカウントの管理者から「アカウント作成したよ」と渡される認証情報はIAMユーザーの認証情報ですね。
さて、この一度作成したAWSアカウントをしょっちゅう作り変える、という話も聞きませんし、コード管理しているとはいえしょっちゅうポリシーを変更して違う権限を付与するようにすると、「できると思っていたことができない」となって作業効率が落ちてしまいます。
なので、特別な作業をする時だけふだん使わない権限を一時的に使える仕組みがあると便利ですね。
そういうときは、IAMロールを作成して、IAMロールに特別な作業のための権限を記述したポリシーをアタッチします。
次に、誰がそのロールを利用できるのか、を記述した信頼ポリシーをたとえば、
{ "Version": "2012-10-17", "Statement": { "Effect": "Allow", "Action": "sts:AssumeRole", "Principal": { "AWS": "arn:aws:iam::AWS-account-ID:user/user-name" }, "Resource": "arn:aws:iam::ACCOUNT-ID-WITHOUT-HYPHENS:role/Test*" } }
のように定義して*2作成したロールに付与すると、必要なときにAssumeRole APIを利用して*3そのロールを引き受ける*4ことで、一時的にふだん利用しているアカウントのものとは異なる権限が一時的に利用できるようになります。
また、「誰がそのロールを利用できるのか」を記述した信頼ポリシーに対して、一時的に付与したい実際やりたい作業のためのアクセス権限(「~~をするためにxxが必要なんですが」)を記述したポリシーをアクセス権限ポリシーと呼びます。
EC2インスタンス上で動くアプリケーションのための権限付与のためにIAMロールを利用する
このIAMロールは、EC2インスタンスの上でアプリケーションを動かす際の権限の付与にも利用できます*5。
どういうことかというと、たとえば、IAMロールを利用しない場合、EC2インスタンス専用のIAMユーザーを用意して、EC2インスタンス上の設定ファイルにはそのユーザーの認証情報を書き込む、といった方法が考えられるんですが、認証情報を管理者以外がsshして見るかもしれないような場所におきたくないですよね。
IAMロールを利用して権限を付与する場合、EC2インスタンスの起動設定にアプリケーションで利用するロールを指定するだけでそのロールに付与されたポリシーに沿った権限がEC2インスタンスのアプリケーションから利用できるようになります。
ただし、EC2インスタンスがロールを利用する場合は、アクセス権限ポリシーの書き方はユーザーがロールを利用する場合と同じなのですが、信頼ポリシーの書き方がユーザーの場合と少し変わっていて、ロールを引き受けるリクエストを投げるのがEC2インスタンス=EC2のサービスだということで、
{ "Version": "2012-10-17", "Statement": { "Sid": "TrustPolicyStatementThatAllowsEC2ServiceToAssumeTheAttachedRole", "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" } }
のように、「誰がそのロールを利用できるのか」の「誰が」の部分を指定する Principal
*6 の値が、
- キーが
"Service"
- 値が
"ec2.amazonaws.com"
となっています*7。
EC2で何か具体的なアプリケーションの利用例、、ではないですが、具体例として
場合のTerraformの設定ファイルでも眺めてしめます。
具体例: EC2インスタンスをECSのコンテナインスタンスとして動かすための権限を付与する
refs:
- https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/instance_IAM_role.html
- https://www.terraform.io/docs/providers/aws/r/iam_role.html
- https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_use_switch-role-ec2_instance-profiles.html
// 信頼ポリシーをロールに付与 resource "aws_iam_role" "ec2_instance_role" { name = "ec2_instance_role" assume_role_policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Action": "sts:AssumeRole", "Principal": { "Service": "ec2.amazonaws.com" }, "Effect": "Allow", "Sid": "" } ] } EOF }
// アクセス権限ポリシー resource "aws_iam_policy" "ecs_container_instance_policy" { name = "ecs_container_instance_policy" path = "/" policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ecs:CreateCluster", "ecs:DeregisterContainerInstance", "ecs:DiscoverPollEndpoint", "ecs:Poll", "ecs:RegisterContainerInstance", "ecs:StartTelemetrySession", "ecs:UpdateContainerInstancesState", "ecs:Submit*", "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "*" } ] } EOF }
// 信頼ポリシーが付与されているロールにさらにアクセス権限ポリシーを付与 resource "aws_iam_role_policy_attachment" "ecs_container_instance_policy_attachment" { role = "${aws_iam_role.ec2_instance_role.name}" policy_arn = "${aws_iam_policy.ecs_container_instance_policy.arn}" }
// EC2インスタンスにはインスタンスプロファイルを利用してロールをアタッチ resource "aws_iam_instance_profile" "ecs_container_instance_profile" { name = "ecs_container_instance_profile" role = "${aws_iam_role.ec2_instance_role.name}" }
resource "aws_instance" "ecs_container_instance" { ... iam_instance_profile = "${aws_iam_instance_profile.ecs_container_instance_profile.name}" }
具体例: ECS上で動くタスクにタスクロールを付与する
refs:
- https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_IAM_role.html
- https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_definition_parameters.html#task_role_arn
// タスクに付与する信頼ポリシーのプリンシパルは ecs-tasks.amazonaws.com resource "aws_iam_role" "task_role" { name = "task_role" assume_role_policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Action": "sts:AssumeRole", "Principal": { "Service": "ecs-tasks.amazonaws.com" }, "Effect": "Allow", "Sid": "" } ] } EOF }
// たとえばタスクにS3のListXxx APIへのリクエストを許可するアクセス権限ポリシーを書いてみる resource "aws_iam_policy" "task_role_policy" { name = "task_role_policy" path = "/" policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:List*", ], "Resource": "*" } ] } EOF }
// 信頼ポリシーが付与されているロールにさらにアクセス権限ポリシーを付与 resource "aws_iam_role_policy_attachment" "task_role_policy_attachment" { role = "${aws_iam_role.task_role.name}" policy_arn = "${aws_iam_policy.task_role_policy.arn}" }
// タスク定義を書く時 TaskRole として先ほど作成したロールを指定する resource "aws_ecs_task_definition" "ecs_task" { family = "ecs_task" task_role_arn = "${aws_iam_role.task_role.arn}" container_definitions = <<DEFINITION [ { "essential": true, "image": "nginx:latest", "memoryReservation": 512, "name": "nginx" } ] DEFINITION }
思ったよりまとまらなかった...。現場からは以上です。
*1:IAMユーザーやIAMグループ, IAMロールなどの違いについてはこちら https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id.html
*2:詳細 https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_elements_principal.html
*3:AssumeRole APIを利用する場合の詳細は https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_use_switch-role-api.html 他。CLIの場合はsource_profileにロールを引き受ける権限があるプロファイル、role_arnに引きうけたいロールのARNを指定したプロファイルを作成する https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_use_switch-role-cli.html
*4:ロールをアタッチされて権限を切り替えることと同じくらいの意味で「ロールを引き受ける」という言葉を使うみたいです https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_terms-and-concepts.html など。なお、ロールを引き受けている間は、元のアクセス権限は利用できません
*5:外部アカウントのユーザーへのアクセス許可など、もっと他のユースケースについては https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_common-scenarios.html など
*6:IAMユーザー、IAMグループ、IAMロールといったIAMポリシーがアタッチされる対象をIAMの用語で「プリンシパル」といいます
*7:https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_use_passrole.html
Terraformで外部の設定ファイルから設定を読み込む(file, template_file, template_cloudinit_config)
今日はとりあえずTerraformのTemplateプロバイダーを少しさわってみようと思います*1。
TerraformのTemplateプロバイダーには、データリソース*2として
- template_file
- template_cloudinit_config
リソースとして
- template_dir*3
がありますが、今回は template_file
を試してみます。
また、これらの前にファイル利用つながりで file
組み込み関数もためして、 template_cloudinit_config
データリソースにも少しだけふれます。
TL;DR
- 外部に書いた設定ファイルの内容を
file
組み込み関数で読み出す template_file
データリソースを使って一部の設定を変数で置き換えられるようにする- EC2の起動時の設定で
userdata
とcloud-init
の両方を使う場合はtemplate_cloudinit_config
データリソースを使うと書きやすい
外部に書いた設定ファイルの内容を file
組み込み関数で読み出す
TerraformでAWSのリソースの設定を書いていると、JSONなどHCLで書きにくい部分があると思います。
そういった部分を外部ファイルに書き出して、 file
組み込み関数で必要な箇所で読みだすことができます。
たとえば、
resource "aws_iam_role" "test_role" { name = "test_role" assume_role_policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Action": "sts:AssumeRole", "Principal": { "Service": "ec2.amazonaws.com" }, "Effect": "Allow", "Sid": "" } ] } EOF }
というtfファイルを書いた時、 <<EOF
から EOF
の部分を外部ファイルに抜き出して、
{ "Version": "2012-10-17", "Statement": [ { "Action": "sts:AssumeRole", "Principal": { "Service": "ec2.amazonaws.com" }, "Effect": "Allow", "Sid": "" } ] }
resource "aws_iam_role" "test_role" { name = "test_role" assume_role_policy = "${file("./test_policy.json")}" }
のように書くことができます。こうすることで、設定の方もシンタックスハイライトが効く環境で編集できるので捗りそうですね。
template_fileデータリソースを使って一部の設定を変数で置き換えられるようにする
設定は外部ファイルで書きたいのだけれど、設定の中で一部だけ変数を利用したい、という場合があります。
この場合は template_file
データリソースを利用すると便利です。
たとえば、
resource "aws_iam_policy" "test_role_app" { name = "test_role_app" policy = "${file("./test_policy.json")}" }
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ecs:Describe*", "ecs:List*" ], "Resource": "arn:aws:ecs:region:account:cluster/my-cluster" } ] }
のようなIAMポリシーがあったとして、対象のリソースだけを変えて同じようなアクションを許可するポリシーを他にも作りたい場合、template_file データリソースが使えます。
// ファイル名は test_policy.json から test_policy.json.tpl に変更 { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ecs:Describe*", "ecs:List*" ], "Resource": "arn:aws:ecs:region:account:cluster/${cluster_name}" } ] }
のように外部設定ファイルを編集すると、 ${cluster_name}
の部分を template_file
データリソースのブロック中で設定できます。
data "template_file" "test_policy" { template = "${file("./test_policy.json.tpl")}" vars { cluster_name = "my_cluster" } } resource "aws_iam_policy" "test_role_app" { name = "test_role_app" policy = "${data.template_file.test_policy.rendered}" }
EC2の起動時の設定でuserdataとcloud-initの両方を使う場合は template_cloudinit_config データリソースを使うと書きやすい
先ほどの template_file
の設定は、EC2の起動時の設定で userdata
を利用してECSのクラスターを指定する、といった設定の一部だけ置き換えたい、といった場合に便利そうですが、 ECSの設定は userdata
だけでなく cloud-init
を利用して行うことも可能です*4。
そして、単純に userdata
と cloud-init
のそれぞれの設定を書いた場合、両者の設定を共存させることはできません。
userdata
と cloud-init
の設定を共存させたい場合、両者の設定をマルチパート形式のテキストに変換し、userdata
に書き込む必要があります。
これはなかなか大変ですが、 template_cloudinit_config
データリソースの part
属性を利用するとこの変換作業を行う必要がなくなります。
現場からは以上です。
*1:今日からコマンドじゃなくてソフトウェアとしてのTerraformはTerraformと表記することにする
*2:プロバイダのページの見出し(たとえば、Templateプロバイダ https://www.terraform.io/docs/providers/template/index.html )ではData Sourcesと書いてあるが、だいたい日本語だとデータリソースと出てくる(たとえば、Terraform for さくらのクラウド データリソースとは https://sacloud.github.io/terraform-provider-sakuracloud/configuration/resources/data_resource/ )
*3:http://febc-yamamoto.hatenablog.jp/entry/2018/02/05/193735 の記事がTemplateプロバイダーのデータリソース、リソースについて詳しいです
*4:https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/user-data.html#user-data-cloud-init