AWS環境で、クロスアカウントでCI/CDしたい。とりあえずBuildフェーズはいらなくてDeployだけでいい。Deployの実行はパイプラインあり/なし両方可能。どちらも単一アカウント内なら複雑な設定もなく比較的容易にできることはわかっているが、クロスアカウントとなると何かと面倒だ。でもやってみる。ここではまずはパイプラインなしとする。
参考
異なる AWS アカウントでアプリケーションをデプロイする
(上記ページにリンクあり。assumeロールの設定は以下参考)
IAM チュートリアル: AWS アカウント間の IAM ロールを使用したアクセスの委任
環境前提
配布元となるAWS開発環境(Dev)にCodeCommitのローカルリポジトリがあり、そこから別アカウントの検証環境(Stg)にデプロイする。その先には本番環境がある想定だが構成は同じになるはず。
① 配布元(Dev)
② 配布先(Stg)
概要
①配布元のアカウントから②配布先のec2にデプロイ可能とするため、②配布先アカウント側で①アカウントのassumeを可能とするIAMロールを作成する。(ロールAとする)① 配布元アカウント側でロールAにassumeし、デプロイを実行する。
基本的に必要となるのはIAM周りの設定であり、ネットワーク系の特別な実装は必要ない。
作業内容
-
配布先②アカウントにて、配置用のS3バケットを作成する。IAMロールのポリシーでバケットへのアクセス権限を定義するため、バケットポリシーは設定しなくても問題なし。(注1)
-
配布先②アカウントにて、①がassumeするためのロールAを作成する。
ロールAで定義する内容 (1) 信頼ポリシーで②のアカウントIDを指定してassumeを許可する。このときrootか②側のIAMロールどちらかを指定する。
rootに設定した場合は、①アカウントでデプロイを実行するユーザのグループにassume可能とするインラインポリシーを適用する。
IAMに設定した場合は、①アカウントでデプロイを実行するec2にこのIAMロールを適用する。実行環境がec2の場合はこれでよいが、クライアント端末の場合はrootにする。
インラインポリシー例 (①アカウントで設定) デプロイ実行ユーザが所属するグループの画面を開き、[アクセス許可] タブ –> [アクセス許可の追加] –> [インラインポリシーの作成] [JSON] タブ選択
以下の内容を設定する。
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::②配布先のアカウントID:role/ロールA"
}
}
(2) ①のアカウントが資材配置用のS3にアクセスするための権限を定義したポリシーを適用する。ちゃんと書いてないけど以下にcodedeploy, ec2の操作権限も追加する。codedeployの権限は何が必要かわからないのでとりあえず全許可にしておいた。ECSへのデプロイだとec2のterminate権限が必要みたいだが、今回の場合ec2は参照のみでOKだと思う。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListAllMyBuckets",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": "arn:aws:s3:::staging-app" #検証環境の資材格納バケット名
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::staging-app/*"
}
]
}
- ②配布先アカウントにて、deployのアプリケーションとデプロイメントグループを作成する。詳細は割愛。
- ①配布元アカウントのec2(または同アカウントのcredentialsをセットした端末)にログインし、ロールAにスイッチする。ちなみにマネジメントコンソールでもスイッチして作業可能だが、deployのpushコマンドがCLIでしかできないため、ここではCLI前提で話を進める。
この時先で作成したロールAにスイッチするため、以下のコマンドを実行する。
$ aws sts assume-role --role-arn "②配布先のアカウントID:role/ロールA" --role-session-name "deployment-test"
すると以下の形式の認証情報が出力される。
{
"Credentials": {
"AccessKeyId": "[access key id]"
"SecretAccessKey": "[secret access key id]",
"SessionToken": "[token id]",
"Expiration": "2021-09-20T15:08:00Z"
}
}
上記を環境変数にセットする。Windowsの場合はexportをsetに変更する。
$ export AWS_ACCESS_KEY_ID=[access key id]
$ export AWS_SECRET_ACCESS_KEY=[secret access key id]
$ export AWS_SESSION_TOKEN=[token id]
これでセッション保持期間の間(expireの時刻)は②アカウントのロールAの権限で作業が可能となる。セッション時間はデフォルトで1時間だが、伸ばしたい場合は--duration-seconds
オプションを使う。(2時間なら7200、3時間なら10800と指定。ロールAの最大セッション期間がそれに応じた時間に設定されている前提)
- デプロイ実行
最初にやる時はデプロイ前にaws s3 cpを実行して、対象S3バケットへ読み書き可能かチェックしておくとよい。
CLIでpush [オプション]を実行し、S3に資材を格納する。この時 3.で作成したアプリケーション名を指定する。(これによりただ単にS3に資材を配置するのではなく、資材をアプリケーションのリビジョンと関連付けることになる)sourceはここではCodeCommitのローカルリポジトリパスを指定しているが、指定するのはappspec.ymlを配置したディレクトリとなる。
$ aws deploy push ¥
--application-name [aplication-name] ¥
--s3-location s3://[staging-app]/[staging-app-key] ¥
--ignore-hidden-files ¥
--source /path/to/source
pushが成功すると資材がzip形式で格納される。ターミナル上ではE-Tagを含む実行コマンド情報が標準出力される。詳細は割愛するがこれを元にcreate-deploymentにてデプロイを実行する。この時 3.で作成したデプロイメントグループを指定する。成功すれば②配布先となるec2にS3から資材が配置される。ちなみにpushはコンソールから実行できないが、デプロイは可能である。しかしそのために実行画面を切り替えるのも面倒なので(コンソールでもassumeする)、ここは引き続きCLIでやる方が自然かと。
(注1) 配布元①アカウントにバケットを作成してもよいが、また追加の設定が必要となる。今回は配布先②に作成した。
その他ポイント
最初deployのコマンドは通ったがその先で失敗した。この時コンソール上では以下のエラーが表示されていた。
The overall deployment failed because too many individual instances failed deployment, too few healthy instances are available for deployment, or some instances in your deployment group are experiencing problems.
これだけではわからないのでログを確認してみたところ、こんなエラーが繰り返し吐かれていた。
/var/log/aws/codedeploy-agent/codedeploy-agent.log
InstanceAgent::Plugins::CodeDeployPlugin::CommandPoller: Missing credentials - please check if this instance was started with an IAM instance profile
確かに配布先ec2にはIAMロールをアタッチしていなかったため、インスタンスプロファイルが存在しない。するとcodedepoloyエージェントが上記のログを吐くわけだ。配布先のec2にインスタンスプロファイルを割り当てるため、別途IAMロールを作成してIAMロールをアタッチしたところ成功した。
IAMをアタッチしても同じエラーが出る場合、エージェントを再起動してみる。候補が表示されなかったりエラーになったりしてIAMのアタッチ自体が不可能な場合は、インスタンスを一旦停止して再試行してみる。