🌊

EKSでRailsをCI/CDする(Kubernetes CI/CDまでの道②)

2022/03/23に公開

はじめに

前回はEKSへのデプロイに挑戦しました。そこで今回はEKSを利用したCI/CDを行っていきます。

今回のハンズオンはこちらのリポジトリを利用します。

また、ベース環境、RDS、EKSのクラスター作成までは各自行っている前提でハンズオンを進めていきます。(前回行いました)

開発環境

  • WSL2 20.04 LTS
  • Docker 20.10.12
  • docker-compose v2.2.3
  • Git 2.25.1
  • AWS CLI 2.4.25
  • eksctl 0.87.0
  • kubectl v1.23.4

参考書籍

このハンズオンでは以下の書籍を参考にしています。

Kubernetes on AWS ~アプリケーションエンジニア 本番環境へ備える

また、基本的なインフラ環境(VPC, subnetなど)はAWSではじめるインフラ構築入門 安全で堅牢な本番環境のつくり方を参考にしています。

詳しく知りたい方は書籍を読んでいただければと思います。

Kubernetes CI/CDまでの道シリーズ

方針

EKSのCI/CDはCodeBuildを利用することで実現が可能です。
Rails(Docker)をCodeBuildでCIする (CI/CDまでの道⑧)で利用したCodeBuildを行った後に、デプロイを行うビルドプロジェクトを実行してデプロイすることにします。

デプロイで行うことは
① CodeBuildにEKSを利用できる環境(AWS CLI、Kubectlのインストール)を用意
② kubectl applyでデプロイメントの更新

を行っています。ローカルで更新するのをCodeBuildの環境で行うので難しくはありません

前準備

/configcredentials.ymlmaster.keyを追加します。

ECRにsample-railssample-nginxを作成してイメージのPushを行います。プッシュコマンド通りに実行をしますが、2つ目を変更します。

# Railsの2つ目のコマンド
$ docker build --target build  --no-cache -t sample-rails .
# Nginxの2つ目のコマンド
$ docker build -f ./containers/nginx/Dockerfile  -t sample-nginx .

デプロイの準備

デプロイを行う上でいくつかのファイルを用意していきます。

まずは/cicd/kustomizationのフォルダを作成します。

/cicd/kustomizationprodというフォルダを作成して以下のファイルを追加します。

kustomization.yml

/cicd/kustomization/prod/kustomization.yml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

bases:
- ../base # ベースとなるマニフェストの場所

images:
  - name: rails-image # ここは変更しない
    newTag: 0.1.1 # アプリケーションのバージョン番号(ここをgit pushする前に毎回変更することでECRのタグになり、applyでイメージ変更することができる)
    newName: [ユーザーID].dkr.ecr.ap-northeast-1.amazonaws.com/sample-rails # ECRレジストリのURI

ユーザーIDを埋めてください。
newTaggit pushするたびに変更します。Railsイメージのタグに利用しています。

/cicd/kustomizationbaseというフォルダを作り、以下の3つのファイルを作成します。

kustomization.yml

cicd/kustomization/base/kustomization.yml
resources:
- deployment.yml
- service.yml

deployment.yml

cicd/kustomization/base/deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: rails
  labels:
    app: rails
spec:
  replicas: 2
  selector:
    matchLabels:
      app: rails
  template:
    metadata:
      labels:
        app: rails
    spec:
      volumes:
        - name: public-data
          emptyDir: {}
        - name: tmp-data
          emptyDir: {}

      initContainers:
      - name: pre-rails
        # image: [ECRのRailsイメージURL]
        image: rails-image
        command: ['/bin/sh', '-c', 'cp -a /myapp/public/* /mnt/empty-dir-content/']
        volumeMounts:
          - name: public-data
            mountPath: "/mnt/empty-dir-content/"

      containers:
      - name: rails
        image: rails-image
        # image: [ECRのRailsイメージURL]
        ports:
          - containerPort: 3000
        command: ['/bin/sh', '-c', 'bundle exec puma -C config/puma.rb']
        env:
          - name: DB_USERNAME
            # value: root
            value: admin
          - name: DB_PASSWORD
            value: password
          - name: DB_DATABASE
            value: myapp
          - name: DB_HOST
            value: 	[RDSのエンドポイント]
            # value: mysql-server
          - name: SECRET_KEY_BASE
            value: [master.keyの値]
        volumeMounts:
        - name: tmp-data
          mountPath: /myapp/tmp/sockets

      - name: nginx
        # image: [ECRのNginxイメージURL]
        image: [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/sample-nginx
        ports:
          - containerPort: 80
        volumeMounts:
        - name: public-data
          mountPath: /myapp/public
        - name: tmp-data
          mountPath: /myapp/tmp/sockets
        resources:
          requests:
            cpu: 100m
            memory: 512Mi
          limits:
            cpu: 250m
            memory: 769Mi

ユーザーIDとRDSのエンドポイント、master.keyの値を各自埋めてください。

service.yml

/cicd/kustomization/base/service.yml
apiVersion: v1
kind: Service
metadata:
  name: rails-server
spec:
  type: LoadBalancer
  selector:
    app: rails
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

次にルートディレクトリにapplyspec.ymlを作成します。
このファイルをCodeBuildで利用することでデプロイを行います。

applyspec.yml

applyspec.yml
version: 0.2
 
phases:

  pre_build:
    commands:
      # Install AWS CLI
      - apt-get update
      - apt install -y wget python
      - wget https://bootstrap.pypa.io/get-pip.py
      - python get-pip.py
      - pip install awscli --upgrade

      # Install kubectl 
      - export KUBECTL_VERSION=1.23.0
      - wget https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl
      - chmod 755 kubectl 
      - mv kubectl /usr/local/bin/

      # Set up kubectl
      # - aws --region us-east-2 eks update-kubeconfig --name kaizawa-eks-test-20190222
      - export EKS_CLUSTER_NAME=eks-work-cluster
      - export REGION=ap-northeast-1
      - aws --region ${REGION} eks update-kubeconfig --name ${EKS_CLUSTER_NAME}
      - kubectl

  build:
    commands:
      # Apply to EKS-Cluster.
      - kubectl get all
      - kubectl kustomize cicd/kustomization/prod
      - kubectl apply -k cicd/kustomization/prod
      - kubectl get all

また、カレントディレクトリにあるbuildspec.ymlも修正します。

buildspec.yml
version: 0.2

env:
  variables:
    DOCKER_BUILDKIT: "1"

phases:
  pre_build:
    commands:
      - echo Logiging in to Amazon ECR...
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
      - docker pull $AWS_ECR_BUILD_REPOSITORY:latest
      - docker tag $AWS_ECR_BUILD_REPOSITORY:latest build:latest

      - IMAGE_TAG=$(echo ${CODEBUILD_RESOLVED_SOURCE_VERSION} | head -c 7)
      - IMAGE_NAME=sample-railis

  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - docker build --target production --cache-from build:latest --build-arg BUILDKIT_INLINE_CACHE=1 -t $IMAGE_NAME:$IMAGE_TAG .
      - docker build --target build --cache-from build:latest --build-arg BUILDKIT_INLINE_CACHE=1 -t build:latest .
      # - docker tag $IMAGE_NAME:$IMAGE_TAG $AWS_ECR_BUILD_REPOSITORY:$IMAGE_TAG

      # install yq
      - wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
      - chmod a+x /usr/local/bin/yq
      - VERSION_NO=`cat cicd/kustomization/prod/kustomization.yml |  yq .images[].newTag`

      - docker tag $IMAGE_NAME:$IMAGE_TAG $AWS_ECR_BUILD_REPOSITORY:$VERSION_NO
      - docker tag build:latest $AWS_ECR_BUILD_REPOSITORY:latest

  post_build:
    commands:
      - echo Build completed on `data`
      - echo Pushing the Docker image...
      - docker images
      - docker push $AWS_ECR_BUILD_REPOSITORY:latest
      # - docker push $AWS_ECR_BUILD_REPOSITORY:$IMAGE_TAG
      - docker push $AWS_ECR_BUILD_REPOSITORY:$VERSION_NO
      - echo "[{\"name\":\"sample-rails\",\"imageUri\":\"${AWS_ECR_BUILD_REPOSITORY}:${IMAGE_TAG}\"}]" > imagedefinitions.json

artifacts:
  files:
    - imagedefinitions.json
    - appspec.yml
    - taskdef.json

実行した際にcicd/kustomization/kustomization.ymlで設定したタグのついたイメージをECRにPushするように変更しました。

リポジトリの作成

Gitのリポジトリを作成してPushしておきます。
ここでは「test2」というリポジトリを作成しました。

前準備

Rails(Docker)をCodeBuildでCIする (CI/CDまでの道⑧)の通りにCodeBuildでテストとビルドがCodePipelineで動くところまで準備を行います。

1度動かして0.1.0のイメージをPushします。
このあとデプロイメントの立ち上げに利用します。

アクセス権限の追加

そこからデプロイをパイプラインに追加します。

まずはクラスターに接続できるように以下のコマンドをWSL2で実行します。

$ eksctl create iamidentitymapping --region ap-northeast-1 --username codebuild --group system:masters --name eks-work-cluster --role arn:aws:iam::[ユーザーID]:role/sample-build-role

これでクラスター接続がCodeBuildからできるようになるのでkubectlコマンドが実行できるようになります。
また。ロールの指定で/service-roleを省略して設定することもポイントです。

次にCodeBuildにポリシーを追加します。

以下のポリシーを「IAM」→「ポリシー」→「ポリシーの作成」で以下を入力します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecr:GetDownloadUrlForLayer",
                "ecr:BatchGetImage",
                "ecr:CompleteLayerUpload",
                "ecr:GetAuthorizationToken",
                "ecr:UploadLayerPart",
                "ecr:InitiateLayerUpload",
                "ecr:BatchCheckLayerAvailability",
                "ecr:PutImage"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": "eks:DescribeCluster",
            "Resource": "arn:aws:eks:*:*:cluster/*"
        }
    ]
}

ポリシー名をここでは「eks-access-codebuild」として作成しました。
作成したらCodeBuildのロール(sample-build-role)にアタッチします。

この設定を行うことでCodeBuildからEKSへのアクセスを行うことができます。

(2022年3月にはIAMを開いたあとにバージニア北部にリージョンが変わるバグがあるので東京に戻してください)

CodeBuild作成

テストやビルドと同じ流れで作成していきます。

項目名
プロジェクト名 apply
ソースプロバイダ GitHub
リポジトリ GitHubアカウントのリポジトリ
GitHubリポジトリ test2 (作成したリポジトリ名)
オペレーティングシステム Ubuntu
ランタイム Standard
イメージ aws/codebuild/standard:5.0
イメージのバージョン aws/codebuild/standard:5.0-21.10.15
特権付与
サービスロール 既存のロール
ロールのARN 作成したロール
Buildspec 名 - オプショナル applyspec.yml

後は今まで通りで作成して大丈夫です

CodePipelineに追加

最後にいま作成したapplyを追加します。
追加の設定などは不要です。

項目名
アクション名 apply
アクションプロバイダー AWS CodeBuild
入力アーティファクト SourceArtifact
プロジェクト名 apply

一番下に追加します。

「保存」をクリックしてパイプラインの設定は終わりです。

Railsのデプロイ

./kubernetes/deployment_rails.ymlの以下の箇所を修正します。

  • RailsとNginxのイメージをECRのURLにする
  • Railsのイメージの末尾に:0.1.0を追加
  • 環境変数DB_USERNAMEをadminに変更
  • 環境変数DB_HOSTをRDSのエンドポイントに変更
  • 環境変数SECRET_KEY_BASEを追加してmaster.keyの値をいれる
./kubernetes/deplpyment_rails.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: rails
  labels:
    app: rails
spec:
  replicas: 2
  selector:
    matchLabels:
      app: rails
  template:
    metadata:
      labels:
        app: rails
    spec:
      volumes:
        - name: public-data
          emptyDir: {}
        - name: tmp-data
          emptyDir: {}

      initContainers:
      - name: pre-rails
        # image: [ECRのRailsイメージURL]
        image: [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/sample-rails:0.1.0
        command: ['/bin/sh', '-c', 'cp -a /myapp/public/* /mnt/empty-dir-content/']
        volumeMounts:
          - name: public-data
            mountPath: "/mnt/empty-dir-content/"

      containers:
      - name: rails
        # image: [ECRのRailsイメージURL]
        image: [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/sample-rails:0.1.0
        ports:
          - containerPort: 3000
        command: ['/bin/sh', '-c', 'bundle exec puma -C config/puma.rb']
        env:
          - name: DB_USERNAME
            # value: root
            value: admin
          - name: DB_PASSWORD
            value: password
          - name: DB_DATABASE
            value: myapp
          - name: DB_HOST
            value: [RDSのエンドポイント]
            # value: mysql-server
          - name: SECRET_KEY_BASE
            value: [master.keyの値]
        volumeMounts:
        - name: tmp-data
          mountPath: /myapp/tmp/sockets

      - name: nginx
        # image: [ECRのNginxイメージURL]
        image: [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/sample-nginx
        ports:
          - containerPort: 80
        volumeMounts:
        - name: public-data
          mountPath: /myapp/public
        - name: tmp-data
          mountPath: /myapp/tmp/sockets
        resources:
          requests:
            cpu: 100m
            memory: 512Mi
          limits:
            cpu: 250m
            memory: 769Mi

master.keyはGitには上がらないため環境変数として設定する必要があります。

以下のコマンドでデプロイを行います。

$ kubectl apply -f ./kubernetes/deployment_rails.yml
$ kubectl get all
# 別のターミナルを開く
$ kubectl exec -it [ポッドの名前] sh
$ rails db:create

$ kubectl port-forward [ポッドの名前] 8080:80

localhost:8080/testにアクセスできることを確認します。

確認ができたらCtrl+Cできります。

CI/CDの確認

/app/views/test/index.html.hamlを以下に変更します。

/app/views/test/index.html.haml
%h1 デプロイtest
%h2 CSSが適応されると色が変わる
%button.btn.btn-primary{:type => "button"} Bootstrap適応
= I18n.t("word.greeting.hello")

変更ができたらGitにPushします。

$ git add .
$ git commit -m "view変更"
$ git push origin main

パイプラインが実行されるのですべてが成功しているか確認します。

更新を確認するため再度アクセスしてみます。

$ kubectl port-forward [ポッドの名前] 8080:80

localhost:8080/testにアクセスして変更されていれば成功です。

注意としてはcicd/kustomiation/prod/kustomization.ymlのタグを毎回変えてPushしないとイメージの更新がされないのです。

お片付け

以下のコマンドでEKS関連を削除します。

$ kubectl delete deployment rails
$ kubectl delete service rails-server
$ kubectl delete deployment mysql-server
$ kubectl delete service mysql-server
$ kubectl delete service kubernetes
$ eksctl delete cluster --name eks-work-cluster

CloudFormationで立ち上げたものをすべて削除
ECRで作成した2つのリポジトリの削除
CodeBuildのプロジェクト3つ削除
CodePipeline削除
CodePipelineで作成されたS3削除
IAMのCodeBuildで作成したポリシーを削除
CloudWatch LogsのCodebuildのログを削除

おわりに

今回はEKSを利用したCI/CDパイプラインを構築しました。
EKSはマネージメントなのでかなり簡単に実現することができました。
タイトルにあるCI/CDはできましたが、引き続きKubernetesの記事をこのタイトルで出していきます。

今回作成したものは以下のリポジトリにあります。

https://github.com/jinwatanabe/Kubernetes_CICD_Road/tree/main/chapter2

参考

CodeBuildでEKSへBuild&Deploy

GitHubで編集を提案

Discussion