🐳

レンティオでのGitHub Actions/AWS連携の事例

2024/09/19に公開

エンジニアのKIMIです。
ちょっと更新頻度が落ちてしまいましたが… 自分のためにも定期的にアウトプットしていこうと思っています。

この記事では、GitHub ActionsでjemallocをインストールしたRubyイメージをビルドし、ECR Publicにプッシュするまでをご紹介します。

なぜ

レンティオではalpineを使っていますので、jemallocを別途make installする必要があります。
(slimにすれば?という声が聞こえてきそうですが、がんばります)

これをインストール済みイメージを使うことで、デプロイ時の数分の待ち時間をなくそうという魂胆です。

やることは次の通りで、

  • OpenID Connectを使って、GitHub ActionsからAWSにアクセス
  • GitHub Actionsでビルド
  • ビルドが終わったらECRにプッシュ

1つひとつはドキュメントに書いてある通りなので難しいわけではありませんが、微妙にドキュメントとは違うこともやっていますので、そのあたり参考になればと思います😀

  • AWS CDKで環境構築
    (レンティオではAWS Copilotで作ったところ以外はAWS CDKで管理している)
  • マルチプラットフォームビルド
    (ECSのARM化終わっていますが、ローカルのPCで若干古いモノが残っているので)
  • ECRはパブリックリポジトリを使う
    (Rubyとjemallocを秘密に扱う必要がないのと、パブリックリポジトリは無料利用枠の恩恵がある)

それでは、用意する3つのファイル(Dockerfile, CDKコード, GitHub Actionsワークフローファイル)を見ていきます

Dockerfile

  • サービスで必要なライブラリを入れたり bundle install しているところを除いてDockerfileを分離し、 docker build するファイルを用意する
Dockerfile
FROM public.ecr.aws/docker/library/ruby:3.3-alpine

RUN apk --update --no-cache add --virtual build-dependencies curl make gcc musl-dev \
    && cd /tmp \
    && curl -L -O https://github.com/jemalloc/jemalloc/releases/download/5.3.0/jemalloc-5.3.0.tar.bz2 \
    && tar -xjf jemalloc-5.3.0.tar.bz2 \
    && cd jemalloc-5.3.0 \
    && ./configure \
    && make \
    && make install \
    && rm -fr /tmp/jemalloc-5.3.0.tar.bz2 /tmp/jemalloc-5.3.0 \
    && apk del --purge build-dependencies

AWS CDK

OIDCプロバイダ

const provider = new iam.OpenIdConnectProvider(this, 'MyProvider', {
  url: 'https://token.actions.githubusercontent.com',
  clientIds: ['sts.amazonaws.com'],
})

const federatedPrincipal = new iam.FederatedPrincipal(
  provider.openIdConnectProviderArn,
  {
    StringEquals: {
      'token.actions.githubusercontent.com:aud': 'sts.amazonaws.com',
    },
    StringLike: {
      'token.actions.githubusercontent.com:sub': 'repo:my-org/my-repo:*',
    },
  },
  'sts:AssumeRoleWithWebIdentity',
)

ECRパブリックリポジトリ

const publicRepositories = [
  new ecr.CfnPublicRepository(this, 'MyRepository', { repositoryName: 'ruby' }),
  // レンティオでは他に、pg_bigmインストール済みPostgreSQLのイメージなどもつくっている
]

IAMロール

const role = new iam.Role(this, 'MyRole', {
  roleName: 'ecr-access-from-github',
  assumedBy: federatedPrincipal,
})

role.addToPolicy(
  new iam.PolicyStatement({
    resources: ['*'],
    actions: ['ecr-public:GetAuthorizationToken', 'sts:GetServiceBearerToken'],
  }),
)
role.addToPolicy(
  new iam.PolicyStatement({
    resources: publicRepositories.map((repo) => repo.attrArn),
    actions: [
      'ecr-public:BatchCheckLayerAvailability',
      'ecr-public:CompleteLayerUpload',
      'ecr-public:InitiateLayerUpload',
      'ecr-public:PutImage',
      'ecr-public:UploadLayerPart',
    ],
  }),
)

GitHub Actions

こちらも公式ドキュメントがありますので、DockerのGitHub Actionsのドキュメント に沿って書いていきます。

ただ、プッシュするのはECRなのでDocker HubにログインしているところをECRにログインするように変える必要があります。

aws-actions/amazon-ecr-login

- uses: aws-actions/configure-aws-credentials@v4
  with:
    aws-region: us-east-1
    # CDKで作成したIAMロールのARN
    role-to-assume: arn:aws:iam::xxx:role/my-github-actions-role
- uses: aws-actions/amazon-ecr-login@v2
  id: login-ecr
  with:
    registry-type: public

docker/build-push-action

  • amd64, arm64用のイメージをビルド & プッシュ
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
  with:
    context: .
    push: true
    platforms: linux/amd64,linux/arm64

まとめ

完成ファイル3つ

ruby-3.3/Dockerfile
ruby-3.3/Dockerfile
FROM public.ecr.aws/docker/library/ruby:3.3-alpine

RUN apk --update --no-cache add --virtual build-dependencies curl make gcc musl-dev \
    && cd /tmp \
    && curl -L -O https://github.com/jemalloc/jemalloc/releases/download/5.3.0/jemalloc-5.3.0.tar.bz2 \
    && tar -xjf jemalloc-5.3.0.tar.bz2 \
    && cd jemalloc-5.3.0 \
    && ./configure \
    && make \
    && make install \
    && rm -fr /tmp/jemalloc-5.3.0.tar.bz2 /tmp/jemalloc-5.3.0 \
    && apk del --purge build-dependencies
lib/github-oidc-stack.ts
lib/github-oidc-stack.ts
import { Stack, StackProps } 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 OpenidConnectStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props)

    const provider = new iam.OpenIdConnectProvider(this, 'MyProvider', {
      url: 'https://token.actions.githubusercontent.com',
      clientIds: ['sts.amazonaws.com'],
    })

    const federatedPrincipal = new iam.FederatedPrincipal(
      provider.openIdConnectProviderArn,
      {
        StringEquals: {
          'token.actions.githubusercontent.com:aud': 'sts.amazonaws.com',
        },
        StringLike: {
          'token.actions.githubusercontent.com:sub': 'repo:my-org/my-repo:*',
        },
      },
      'sts:AssumeRoleWithWebIdentity',
    )

    const publicRepositories = [
      new ecr.CfnPublicRepository(this, 'MyRepository', { repositoryName: 'ruby' }),
    ]

    const role = new iam.Role(this, 'MyRole', {
      roleName: 'my-github-actions-role',
      assumedBy: federatedPrincipal,
    })

    role.addToPolicy(
      new iam.PolicyStatement({
        resources: ['*'],
        actions: ['ecr-public:GetAuthorizationToken', 'sts:GetServiceBearerToken'],
      }),
    )
    role.addToPolicy(
      new iam.PolicyStatement({
        resources: publicRepositories.map((repo) => repo.attrArn),
        actions: [
          'ecr-public:BatchCheckLayerAvailability',
          'ecr-public:CompleteLayerUpload',
          'ecr-public:InitiateLayerUpload',
          'ecr-public:PutImage',
          'ecr-public:UploadLayerPart',
        ],
      }),
    )
  }
}
.github/workflows/main.yml
.github/workflows/main.yml
on:
  push:
    branches:
      - main

jobs:
  pushToECR:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-qemu-action@v3
      - uses: docker/setup-buildx-action@v3
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-region: us-east-1
          role-to-assume: arn:aws:iam::xxx:role/my-github-actions-role
      - uses: aws-actions/amazon-ecr-login@v2
        id: login-ecr
        with:
          registry-type: public
      - uses: docker/build-push-action@v6
        env:
          REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          REGISTRY_ALIAS: my-registry-alias
          REPOSITORY: my-repository-name
        with:
          context: ruby-3.3
          push: true
          platforms: linux/amd64,linux/arm64
          tags: ${{ env.REGISTRY }}/${{ env.REGISTRY_ALIAS }}/${{ env.REPOSITORY }}:3.3.5-alpine3.20

採用情報

レンティオでは絶賛、フルスタックエンジニアを募集しています!
https://www.wantedly.com/companies/rentio

Discussion