💈
GitHub Actions To Amazon ECRをCDKで用意する
はじめに
GitHub ActionsでDocker Imageをビルドして、ECRにPushするやつないかな。と調べたら素晴らしい記事が出てきたので、あっという間に終わってしまった。
新たに書く必要もない気はしつつ、該当の記事はTerraformを前提に置かれていたため、AWS CDK派の人向けに読み替えた手順を公開する。
CDK
Github ActionsからECRへアクセスするために作成したいリソースは、以下だ。
- IAMのOIDC Provider
- Github Actionsに利用させるIAM Role
- (任意)ECR Repository
これをCDKで表現するとこうなる。
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as iam from "aws-cdk-lib/aws-iam";
import * as ecr from "aws-cdk-lib/aws-ecr";
export class GhOidcStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const gitHubIdProvider = new iam.OpenIdConnectProvider(
this,
"GitHubIdProvider",
{
url: "https://token.actions.githubusercontent.com",
clientIds: ["sts.amazonaws.com"],
}
);
const ghUser: string = <GitHub User>;
const ghRepo: string = <GitHub Repo>;
const ghBranch: string = <GitHub Branch>;
const ghUsercontentSub: string = `repo:${ghUser}/${ghRepo}:ref:refs/heads/${ghBranch}`;
// const ghUsercontentSubAllBranch: string = `repo:${ghUser}/${ghRepo}:*`;
const assumeRoleAction: string = "sts:AssumeRoleWithWebIdentity";
const federatedPrincipal = new iam.FederatedPrincipal(
gitHubIdProvider.openIdConnectProviderArn,
{
StringEquals: {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": ghUsercontentSub,
},
// どのブランチでも動かしたいとき
// StringLike: {
// "token.actions.githubusercontent.com:sub": ghUsercontentSubAllBranch
// }
},
assumeRoleAction
);
const oidcDeployRole = new iam.Role(this, "GitHubOidcRole", {
roleName: "github-oidc-role",
assumedBy: federatedPrincipal,
});
const repo = new ecr.Repository(this, "MyTempRepo", {
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteImages: true,
});
repo.grantPullPush(oidcDeployRole);
}
}
CFn,Terraformとの差分
-
iam.OpenIdConnectProvider
を用いることで、フィンガープリントなどを意識せず簡素に書ける。 -
repo.grantPullPush
でほぼPolicy設計が終わるので、仮にCDKのスコープ外でもecr.Repository.fromRepositoryArn
で取得できると便利。
CDKで作成されるIAM Policyの課題
ほぼ、と書いたのはCDKのgrantで作成すると、若干Actionが多いため。
割り切りで使っていい気もするが、厳密に設定したい場合は別途Policyを書くこと。
-
repo.grantPullPush
で付与されるPolicy
"ecr:CompleteLayerUpload",
"ecr:UploadLayerPart",
"ecr:InitiateLayerUpload",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage"
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
- 最小限のIAM Policy
- 公式ドキュメント参照
"ecr:CompleteLayerUpload",
"ecr:UploadLayerPart",
"ecr:InitiateLayerUpload",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage"
この課題もコンストラクト側にrepo.grantPush
があれば済む話なので、PullRequestを出している。
Dockerfile
特に何でもいいが、前回のRye記事が好評だったので調子に乗って作ったRyeが動くもの。
Ryeを導入したDockerfile
# Use multi-stage builds
FROM ubuntu:jammy-20230522 AS build
# Set up non-root user
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/home/ryeuser" \
--shell "/bin/bash" \
--uid "${UID}" \
ryeuser
# Update and install dependencies
RUN apt-get update && apt-get install -y \
curl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Set working directory
USER ryeuser
WORKDIR /home/ryeuser
# Set Rye environment variables
ENV RYE_HOME="/home/ryeuser/.rye"
ENV PATH="$RYE_HOME/shims:$PATH"
# Install Rye
RUN curl -sSf https://rye-up.com/get | RYE_NO_AUTO_INSTALL=1 RYE_INSTALL_OPTION="--yes" bash
RUN rye init app && cd app \
&& rye add fastapi uvicorn[standard] \
&& rye sync
# Second stage to create a lean production image
FROM ubuntu:jammy-20230522
COPY /home/ryeuser /home/ryeuser
# Set up non-root user
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
ryeuser
# Switch to non-root user
USER ryeuser
WORKDIR /home/ryeuser/app
# Set Rye environment variables
ENV RYE_HOME="/home/ryeuser/.rye"
ENV PATH="$RYE_HOME/shims:$PATH"
ENTRYPOINT ["rye"]
CMD ["run", "python", "-c", "import fastapi"]
Github Actions
ほぼこれなので、割愛。
現在はaws-actions/configure-aws-credentials@v2
でも動くので書き換えて良さそう。
まとめ
GitHub Actions+AWSはかなり簡単なので、臆さず使っていきたい。用意するリソースもCDKでサッと作れてよかった。
Discussion