TECHSTEP

ITインフラ関連の記事を公開してます。

【メモ】AWS CodeCommit / CodeBuild + Flux を使ったGitHub Flowの構築例

今回はAWS CodeCommit/CodeBuildとFluxを使い、GitHub Flowを実現することを目指して構成を考えてみました。

GitHub Flowとは

GitHub Flowは開発ワークフローの1つであり、以下のようなルールが定められた、比較的シンプルなワークフローになります。

  1. master ブランチは常にデプロイ可能である
  2. 作業用ブランチをmasterから作成する
  3. 作業用ブランチをリモートブランチに定期的にプッシュする
  4. フィードバックや助言が欲しい時、ブランチをマージしたいときは、 プルリクエスト を作成する
  5. プルリクエストが承認されたらmasterへマージする
  6. masterへのマージが完了したら直ちにデプロイを行う

f:id:FY0323:20220119170728p:plain

GitHub Flowでは、運用するコードは1つのバージョンだけを持ち、常にデプロイ可能なメインラインに頻繁にインテグレーションすることを目指します。また通常の機能開発と本番環境向けの修正を同じフローで対応します。このためGit Flowと異なり、リリース専用のreleaseブランチや、本番環境のバグ修正に使うHotfixブランチなどは利用しません。

※参考:

パイプラインの全体像

今回用意したパイプラインの全体像は以下の通りです。今回、アプリケーションコードのテストやコンテナイメージのビルドはAWS CodeBuild、EKS環境へのデプロイは Flux を採用しています。

なお今回GitHub Flowを適用しているのはアプリケーションコードの開発フローのみになります。マニフェストの開発には特にフローを設定していません。

f:id:FY0323:20220119170800p:plain

AWS CodeBuildの部分は、以前試した構成を再利用しています。

EKSなどKubernetesクラスターへのマニフェストの自動デプロイは、以下の2つのどちらかのアプローチを採用するかと思います。今回は2つ目の、GitOpsのアプローチで実行しています。

  • CIOps: アプリケーション変更のマージ時に、コンテナイメージビルドと合わせて kubectl 等でデプロイをする
  • GitOps: コンテナイメージの更新を検知してテスト環境への自動デプロイを実行する

※参考:

ここでは Flux というCI/CD用ツールを利用します。Fluxの利用方法については 以前紹介したことがあるので、そちらをご覧ください。

実際の開発フローは、以下のような流れを想定しています。

f:id:FY0323:20220119174128p:plain

  • 開発者はアプリケーションの機能追加・修正のためにmainブランチからfeatureブランチを作成する
  • featureブランチ上で開発を行い、mainブランチに対するPRを作成する
  • PRの作成を検知して自動テストが実行される
  • テストに合格したらレビューを依頼し、修正などを経てmainブランチへマージする
  • mainブランチへマージされると、コンテナイメージのビルドを実行する
  • 新しいコンテナイメージがPushされるのを検知し、マニフェストのイメージタグの書き換えと、テスト環境への自動デプロイが実行される
  • テスト環境で問題がないことを確認したら、対象のコミットに対しGitタグを付与する
  • Gitタグを検知し、本番環境への自動デプロイが実行される

※参考:

開発フローの流れ

開発者はアプリケーションの機能追加・修正のためにmainブランチからfeatureブランチを作成する

AWSではIAMユーザー・グループに対してIAMポリシーを適用し、CodeCommitに対する操作制限をかけることができます。

今回は testuser というIAMユーザーを作成しました。このユーザーは testgroup というIAMグループに属しており、testgroup にはあらかじめ main ブランチへの直接PushができないようIAMロールを付与しています。

ここでは testusercodecommit-ci というCodeCommitリポジトリをCloneし、作業用のブランチを作成します。

なお、IAMユーザー作成後はAWSマネジメントコンソールにアクセスし、CodeCommit用のアクセス情報を取得します。今回はHTTPS認証を利用しました。

※参考:

featureブランチ上で開発を行い、mainブランチに対するPRを作成する

featureブランチ上でソースコードなどに修正を行い、mainブランチへのPRを作成します。

PRの作成を検知して自動テストが実行される

codecommit-ci でPRが作成されると、AWS EventBridgeを経由して codebuild-ci-test というCodeBuildプロジェクトのジョブが起動します。 codebuild-ci-test ではテストが実行され、実行結果はSlackに通知されます。

f:id:FY0323:20220120203750p:plain

テストに合格したらレビューを依頼し、修正などを経てmainブランチへマージする

CodeBuildによるテストが無事通ったら、レビューを依頼してPRのチェックをします。今回は承認ルールテンプレートも作成しており、PRに対して1件の承認がないとマージできないよう設定しています。

※参考:

mainブランチへマージされると、コンテナイメージのビルドを実行する

PRのレビューで問題がなければ承認・マージを行います。EventBridgeがマージを検知すると codebuild-ci-build というCodeBuildプロジェクトがジョブを開始します。このジョブではDockerfileを用いたイメージのビルド、ECRリポジトリへのPushを行います。ジョブの結果はSlackへ通知されます。

f:id:FY0323:20220120204444p:plain

なお今回コンテナイメージのタグは main-<date>-<Commit IDの先頭7文字> という形式にしています。

※参考:

新しいコンテナイメージがPushされるのを検知し、マニフェストのイメージタグの書き換えと、テスト環境への自動デプロイが実行される

ECRに新しいイメージがPushされると、あらかじめEKSにインストールされたFluxがイメージの更新を検知し、codecommit-cd というCodeCommitリポジトリ上のマニフェストファイルを書き換えます。

マニフェストファイルが書き換わると、Fluxがリポジトリ上の変更を検知し、EKSクラスターへ自動的に更新を行います。これにより、コンテナイメージが更新されることで、テスト環境への自動デプロイを実現します。

f:id:FY0323:20220119171002p:plain

※参考:

テスト環境で問題がないことを確認したら、対象のコミットに対しGitタグを付与する

今回は作成しませんでしたが、テスト環境での動作確認が取れたら、特定のGitタグを付与することで本番環境のPodが更新されることを想定しています。ここでもFluxを利用し、以下のようなマニフェストファイルを使うことで、Gitタグを検知してアップデートを行います。

apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: GitRepository
metadata:
  name: prod
  namespace: flux-system
spec:
  interval: 1m
  url: https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/codecommit-cd
  ref:
    semver: ">=3.1.0-rc.1 <3.2.0" # Gitタグの形式を指定
  gitImplementation: libgit2
  secretRef:
    name: https-credentials

※参考:

Gitタグを検知し、本番環境への自動デプロイが実行される

前項の通り、FluxがGitタグを検知して自動デプロイを実行します。

ここまでで、今回のGitHub Flowの流れは終了です。

環境構築

最後に、今回の環境構築についての紹介です。

CodeCommit / CodeBuildの作成

CodeCommit / CodeBuild関連のリソースは、以下のマニフェストファイルで作成します。ここでは承認ルールテンプレートなども合わせて作成します。承認ルールテンプレートの作成は以前試したこちらの記事をご確認ください。

sample-resources.yaml

AWSTemplateFormatVersion: '2010-09-09'
Description: Sample resources for GitHub flow
 
Parameters:
  CodeCommitRepoNameForCI:
    Type: String
    Default: "codecommit-ci"
  CodeBuildProjectForCITest:
    Type: String
    Default: "codebuild-ci-test"
  CodeBuildProjectForCIBuild:
    Type: String
    Default: "codebuild-ci-build"
  RuleForCITest:
    Type: String
    Default: "rule-ci-test"
  RuleForCIBuild:
    Type: String
    Default: "rule-ci-build"
  CodeCommitRepoNameForCD:
    Type: String
    Default: "codecommit-cd"
  ECRRepoName:
    Type: String
    Default: "test-ecr-cicd"
  NotifySlackChannel:
    Type: String
    Description: Slack Channel ID
    Default: "xxxxxxxxxxx"
  NotifyChatbotWorkspaceId:
    Type: String
    Description: Chatbot Workspace ID
    Default: "xxxxxxxxxxx"
  UserName:
    Type: String
    Default: "testuser"
  UserPassword:
    Type: String
    Default: "testuser@1234"
  GroupName:
    Type: String
    Default: "testgroup"
  RuleTemplateName:
    Type: String
    Default: "testrule"
 
Resources:
  ###########################
  # Sample resources for CI #
  ###########################
  # CodeCommit for CI
  CodeCommitForCI:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: !Ref CodeCommitRepoNameForCI
      RepositoryDescription: CodeCommit repository for CI
  # CodeBuild + IAM for CI
  CodeBuildForCITest:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Ref CodeBuildProjectForCITest
      Description: CodeBuild project for CI test
      ServiceRole: !GetAtt BuildRoleForCITest.Arn
      Artifacts:
        Type: NO_ARTIFACTS
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
      Source:
        Location: !GetAtt CodeCommitForCI.CloneUrlHttp
        Type: CODECOMMIT
        BuildSpec: |
          version: 0.2
 
          phases:
            install:
              runtime-versions:
                golang: 1.14
            build: 
              commands:
                - echo Test the Go code
                - go test -v $CODEBUILD_SRC_DIR/app
  BuildRoleForCITest:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: "Allow"
            Action: 'sts:AssumeRole'
            Principal:
              Service: codebuild.amazonaws.com
  BuildPoliciesForCITest:
    Type: "AWS::IAM::Policy"
    Properties:
      PolicyName: Policy-for-ci-test
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - logs:*
            Resource: '*'
            Effect: Allow
          - Resource: "*"
            Effect: Allow
            Action:
              - 'codebuild:*'
              - 'codecommit:*'
              - 'events:*'
              - 'logs:CreateLogGroup'
              - 'logs:CreateLogStream'
              - 'logs:PutLogEvents'
              - 'iam:*'
      Roles:
        - Ref: BuildRoleForCITest
 
  CodeBuildForCIBuild:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Ref CodeBuildProjectForCIBuild
      Description: CodeBuild project for CI build
      ServiceRole: !GetAtt BuildRoleForCIBuild.Arn
      Artifacts:
        Type: NO_ARTIFACTS
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/docker:18.09.0
        # please set the variables
        EnvironmentVariables:
        - Name: "ECR_REPO"
          Value: !Ref ECRRepoName
        - Name: "BRANCH"
          Value: "main"
      Source:
        Location: !GetAtt CodeCommitForCI.CloneUrlHttp
        Type: CODECOMMIT
        BuildSpec: |
          version: 0.2
 
          phases:
            pre_build:
              commands:
                - echo Login to ECR
                - AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
                - $(aws ecr get-login --no-include-email --region ap-northeast-1)
                - REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$ECR_REPO
                - echo Set IMAGE_TAG
                - DATE=$(date "+%Y%m%d%H%M")
                - COMMITID=$(echo ${CODEBUILD_RESOLVED_SOURCE_VERSION} | head -c 7)
                - IMAGE_TAG=$BRANCH-$DATE-$COMMITID
            build: 
              commands:
                - echo Build Docker image
                - docker build -t $REPOSITORY_URI:$IMAGE_TAG app/
            post_build:
              commands:
                - echo Push to ECR
                - docker push $REPOSITORY_URI:$IMAGE_TAG
  BuildRoleForCIBuild:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: "Allow"
            Action: 'sts:AssumeRole'
            Principal:
              Service: codebuild.amazonaws.com
  BuildPoliciesForCIBuild:
    Type: "AWS::IAM::Policy"
    Properties:
      PolicyName: Policy-for-ci-build
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - logs:*
            Resource: '*'
            Effect: Allow
          - Resource: "*"
            Effect: Allow
            Action:
              - 'ecr:*'
              - 'codebuild:*'
              - 'codecommit:*'
              - 'events:*'
              - 'logs:CreateLogGroup'
              - 'logs:CreateLogStream'
              - 'logs:PutLogEvents'
              - 'iam:*'
      Roles:
        - Ref: "BuildRoleForCIBuild"
  # EventBridge for CI
  EventBridgeForCITest:
    Type: AWS::Events::Rule
    Properties:
      Description: EventBridge for PR test
      EventPattern:
        source:
          - "aws.codecommit"
        resources: 
          - !GetAtt CodeCommitForCI.Arn
        detail-type:
          - "CodeCommit Pull Request State Change"
        detail:
          event:
            - "pullRequestCreated"
          pullRequestStatus:
            - "Open"
          destinationReference:
            - "refs/heads/main"
      Name: !Ref RuleForCITest
      State: "ENABLED"
      Targets:
        - Arn: !GetAtt CodeBuildForCITest.Arn
          Id: AppPRTest
          RoleArn: !GetAtt EventBridgeRoleForCITest.Arn
  EventBridgeRoleForCITest:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: Policy-eventbridge-for-ci-test
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Resource: !GetAtt CodeBuildForCITest.Arn
                Action: 
                  - codebuild:StartBuild
  EventBridgeForCIBuild:
    Type: AWS::Events::Rule
    Properties:
      Description: EventBridge for PR build
      EventPattern:
        source:
          - "aws.codecommit"
        resources: 
          - !GetAtt CodeCommitForCI.Arn
        detail-type:
          - "CodeCommit Pull Request State Change"
        detail:
          event:
            - "pullRequestMergeStatusUpdated"
          pullRequestStatus:
            - "Closed"
          destinationReference:
            - "refs/heads/main"
      Name: !Ref RuleForCIBuild
      State: "ENABLED"
      Targets:
        - Arn: !GetAtt CodeBuildForCIBuild.Arn
          Id: AppPRTest
          RoleArn: !GetAtt EventBridgeRoleForCIBuild.Arn
  EventBridgeRoleForCIBuild:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: Policy-eventbridge-for-ci-build
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Resource: !GetAtt CodeBuildForCIBuild.Arn
                Action: 
                  - codebuild:StartBuild
 
  # CodeCommit for CD
  CodeCommitForCD:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: !Ref CodeCommitRepoNameForCD
      RepositoryDescription: CodeCommit repository for CD
 
  ###########################
  # Sample resources common #
  ###########################
  # ECR
  ECRForCICD:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: !Ref ECRRepoName
      ImageScanningConfiguration: 
        ScanOnPush: "true"
  # Chatbot
  ChatbotSlackConfiguration:
    Type: AWS::Chatbot::SlackChannelConfiguration
    Properties:
      ConfigurationName: chatbot-slack-configuration
      IamRoleArn: !GetAtt ChatbotRole.Arn
      LoggingLevel: ERROR
      SlackChannelId: !Ref NotifySlackChannel
      SlackWorkspaceId: !Ref NotifyChatbotWorkspaceId
  ChatbotRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - chatbot.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/CloudWatchReadOnlyAccess
  # CodeStar Notification Rule
  NotificationRoleForCITest:
    Type: 'AWS::CodeStarNotifications::NotificationRule'
    Properties:
      Name: 'notifications for CI test'
      DetailType: FULL
      Resource: !GetAtt CodeBuildForCITest.Arn
      EventTypeIds: 
        - codebuild-project-build-state-failed
        - codebuild-project-build-state-succeeded
      Targets: 
        - TargetType: AWSChatbotSlack 
          TargetAddress: !GetAtt ChatbotSlackConfiguration.Arn
  NotificationRoleForCIBuild:
    Type: 'AWS::CodeStarNotifications::NotificationRule'
    Properties:
      Name: 'notifications for PR build'
      DetailType: FULL
      Resource: !GetAtt CodeBuildForCIBuild.Arn
      EventTypeIds: 
        - codebuild-project-build-state-failed
        - codebuild-project-build-state-succeeded
      Targets: 
        - TargetType: AWSChatbotSlack 
          TargetAddress: !GetAtt ChatbotSlackConfiguration.Arn
 
  # IAM User
  IAMPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties: 
      Description: prohibit direct push to codecommit
      Path: /
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Deny
            Action: 'codecommit:GitPush'
            Resource: '*'
            Condition:
              "StringEqualsIfExists":
                "codecommit:References": 
                  - "refs/heads/main"
              "Null":
                "codecommit:References": 
                  - "false"
  IAMUser:
    Type: AWS::IAM::User
    Properties:
      Groups:
        - !Ref IAMGroup
      UserName: !Ref UserName
      LoginProfile:
        Password: !Ref UserPassword
        PasswordResetRequired: "false"
  IAMGroup:
    Type: AWS::IAM::Group
    Properties:
      GroupName: !Ref GroupName
      ManagedPolicyArns:
        - !Ref IAMPolicy
        - "arn:aws:iam::aws:policy/AWSCodeCommitPowerUser"
  # Approval Rule Template
  RuleTemplate:
    Type: Community::CodeCommit::ApprovalRuleTemplate
    Properties:
      Name: !Ref RuleTemplateName
      Description: test rule
      Content:
        Version: "2018-11-08"
        DestinationReferences:
          - "refs/heads/main"
        Statements:
          - Type: "Approvers"
            NumberOfApprovalsNeeded: 1
            ApprovalPoolMembers:
              - "*"
  RepoAssociation:
    Type: Community::CodeCommit::RepositoryAssociation
    Properties:
      ApprovalRuleTemplateArn: !Ref RuleTemplate
      RepositoryNames: 
        - !Ref CodeCommitRepoNameForCI

ソースコード・Dockerfileの配置

CodeCommitを作成したら、各リポジトリREADME.md を適当に配置し、ローカルへクローンします。クローンしたら、ソースコード・Dockerfileは codecommit-ci リポジトリapp ディレクトリに、Kubernetesマニフェストファイルは codecommit-cd リポジトリmanifest ディレクトリに配置します。

今回利用したソースコード・Dockerfileはこちらです。

main.go

package main
 
import (
    "fmt"
    "net/http"
)
 
func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "<html><body>hello AWS CI/CD</body></html>\n")
}
 
func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

main_test.go

package main
 
import (
    "net/http"
    "net/http/httptest"
    "testing"
)
 
func TestHandler(t *testing.T) {
    testserver := httptest.NewServer(http.HandlerFunc(handler))
    defer testserver.Close()
 
    res, err := http.Get(testserver.URL)
    defer res.Body.Close()
 
    if err != nil {
        t.Fatalf("failed test %#v", err)
    }
    if res.StatusCode != 200 {
        t.Error("response code is not 200")
    }
}

Dockerfile

FROM golang:1.15.6 as builder
 
WORKDIR /go/src
 
COPY ./main.go ./
ARG CGO_ENABLED=0
ARG GOOS=linux
ARG GOARCH=amd64
RUN go build -o /go/bin/main
 
 
FROM scratch as runner
 
COPY --from=builder /go/bin/main /app/main
 
ENTRYPOINT ["/app/main"]

マニフェストファイルはこちらです。Fluxがコンテナイメージタグを検索できるよう、 # {"$imagepolicy": "flux-system:test-policy"} という記述を追加しています。

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-app
  namespace: default
  labels:
    app: sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sample
  template:
    metadata:
      labels:
        app: sample
    spec:
      containers:
      - name: app
        image: 000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/test-ecr-cicd:main-000000000000-0123abc # {"$imagepolicy": "flux-system:test-policy"}
        ports:
        - containerPort: 8080
 
---
apiVersion: v1
kind: Service
metadata:
  name: sample-app
  namespace: default
spec:
  selector:
    app: sample
  ports:
  - protocol: TCP
    name: http
    port: 80
    targetPort: 8080

※参考:

EKSクラスターの作成

次にEKSクラスターを作成します。今回は eksctl コマンドで作成しました。

$ eksctl version
0.77.0

$ eksctl create cluster -f eks-clusterconfig.yml 

Fluxのインストール

次にEKSクラスターへFluxをインストールします。今回は flux install コマンドを利用しました。

$ flux --version
flux version 0.24.1

$ flux install --components-extra=image-reflector-controller,image-automation-controller

次にFlux用のリソースを作成します。今回はコンテナイメージリポジトリのモニタリングも行うので、以下のように複数のCustom Resourceを利用しています。

  • GitRepository
  • Kustomization
  • ImageRepository
  • ImagePolicy
  • ImageUpdateAutomation

GitRepository Kustomizationのファイルはこちら。

gitrepo.yaml

---
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: GitRepository
metadata:
  name: codecommit-cd
  namespace: flux-system
spec:
  interval: 1m
  url: https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/codecommit-cd
  ref:
    branch: main
  gitImplementation: libgit2
  secretRef:
    name: https-credentials
---
apiVersion: v1
kind: Secret
metadata:
  name: https-credentials
  namespace: flux-system
type: Opaque
data:
  username: <base64 encoded username>
  password: <base64 encoded password>
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
kind: Kustomization
metadata:
  name: codecommit-cd
  namespace: flux-system
spec:
  interval: 5m0s
  path: ./manifest
  prune: true
  sourceRef:
    kind: GitRepository
    name: codecommit-cd
  validation: client

今回はソースコードリポジトリにCodeCommitを利用し、アクセスはHTTPS認証を使っています。FluxからCodeCommitを利用する場合は、こちらのリンク先に書かれている通り、Secret リソースにアクセス情報を定義します。また、Fluxがリポジトリからマニフェストファイルを利用するため、 Kustomization リソースも合わせて作成します。

※参考:

ImageRepository ImagePolicy ImageUpdateAutomation のファイルはこちら。

imagerepo.yaml

---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageRepository
metadata:
  name: test-ecr-cicd
  namespace: flux-system
spec:
  interval: 1m0s
  image: 000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/test-ecr-cicd
  secretRef:
    name: ecr-credentials
---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImagePolicy
metadata:
  name: test-policy
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: test-ecr-cicd
  filterTags:
    pattern: '^main-[0-9].*-[a-f0-9].*'
  policy:
    alphabetical:
      order: asc
---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: test-automation
  namespace: flux-system
spec:
  interval: 1m0s
  sourceRef:
    kind: GitRepository
    name: codecommit-cd
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        email: fluxcdbot@users.noreply.github.com
        name: fluxcdbot
      messageTemplate: '{{range .Updated.Images}}{{println .}}{{end}}'
    push:
      branch: main
  update:
    path: ./manifest
    strategy: Setters

secret-cronjob.yaml

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: ecr-credentials-sync
  namespace: flux-system
rules:
- apiGroups: [""]
  resources:
  - secrets
  verbs:
  - get
  - create
  - patch
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: ecr-credentials-sync
  namespace: flux-system
subjects:
- kind: ServiceAccount
  name: ecr-credentials-sync
roleRef:
  kind: Role
  name: ecr-credentials-sync
  apiGroup: ""
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ecr-credentials-sync
  namespace: flux-system
---
apiVersion: v1
kind: Secret
metadata:
  name: aws-credentials
  namespace: flux-system
type: Opaque
data:
  AWS_ACCESS_KEY_ID: <base64 encoded AWS Access Key>
  AWS_SECRET_ACCESS_KEY: <base64 encoded AWS Secret Access Key>
---
apiVersion: batch/v1
kind: CronJob
metadata:
  name: ecr-credentials-sync
  namespace: flux-system
spec:
  suspend: false
  schedule: 0 */6 * * *
  failedJobsHistoryLimit: 1
  successfulJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: ecr-credentials-sync
          restartPolicy: Never
          volumes:
          - name: token
            emptyDir:
              medium: Memory
          initContainers:
          - image: amazon/aws-cli
            name: get-token
            imagePullPolicy: IfNotPresent
            envFrom:
            - secretRef:
                name: aws-credentials
            env:
            - name: REGION
              value: ap-northeast-1
            volumeMounts:
            - mountPath: /token
              name: token
            command:
            - /bin/sh
            - -ce
            - aws ecr get-login-password --region ${REGION} > /token/ecr-token
          containers:
          - image: bitnami/kubectl
            name: create-secret
            imagePullPolicy: IfNotPresent
            env:
            - name: SECRET_NAME
              value: ecr-credentials
            - name: ECR_REGISTRY
              value: 000000000000.dkr.ecr.ap-northeast-1.amazonaws.com
            volumeMounts:
            - mountPath: /token
              name: token
            command:
            - /bin/bash
            - -ce
            - |-
              kubectl create secret docker-registry $SECRET_NAME \
                --dry-run=client \
                --docker-server="$ECR_REGISTRY" \
                --docker-username=AWS \
                --docker-password="$(</token/ecr-token)" \
                -o yaml | kubectl apply -f -

Amazon ECRを利用する場合は、12時間ごとにアクセストークンが更新されるため、定期的にアクセス情報を更新するための CronJob を用意します。とりあえずアクセス用の Secret リソースを作成したかったので、CronJobからJobを作成する以下のコマンドを実行し、Secretリソースを作成しました。

$ kubectl create job docker-registry –from=cronjob/ecr-credentials-sync
$ kubectl get job -n flux-system
NAME                            COMPLETIONS   DURATION   AGE
docker-registry                 1/1           22s        5h6m

※参考:

パイプラインの実行

ここまでで必要なリソースの作成は完了しました。この状態で codebuild-ci-build のジョブが実行されると、ECRへの新規イメージのPushを検知し、CodeCommit上のマニフェストファイルを書き換えます。

# Image Reflector Controllerのログ
$ kubectl logs image-reflector-controller-6d94666b7d-962x8 -n flux-system

(中略)

{"level":"info","ts":"2022-01-16T06:16:31.391Z","logger":"controller.imagerepository","msg":"reconciliation finished in 43.928032ms, next run in 
1m0s","reconciler group":"image.toolkit.fluxcd.io","reconciler kind":"ImageRepository","name":"test-ecr-cicd","namespace":"flux-system"}

マニフェストファイルの変更を検知すると、FluxはEKSクラスターへのデプロイを実行し、新しいコンテナイメージを持つDeploymentが起動します。

# sample-appのイメージタグが変更されている
$ kubectl get pods
NAME                          READY   STATUS    RESTARTS   AGE
sample-app-7689d7759c-7h56f   1/1     Running   0          83m

$ kubectl describe pod sample-app-7689d7759c-7h56f

(中略)

Containers:
  app:
    Container ID:   docker://b94f50458c881f8b11bbf767dc04b9c12bb5f837392e79651c92e278e745d875
    Image:          000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/test-ecr-cicd:main-202201160619-0ce5ebf

【メモ】AWS CodeCommitのブランチへの操作制限と承認ルールテンプレートの作り方

ソースコードを管理するリポジトリを本格的に運用しようと思うと、リポジトリへの操作に対して制限をかける必要が出てきます。AWS CodeCommitはデフォルトに提供する機能に限りがあり、リポジトリに対する制限をかけるためにいろいろと設定が必要になります。

今回はAWS CodeCommitに対する操作制限をかける方法について調査しました。

ブランチへの操作制限

ブランチへの操作制限は、専用のIAMポリシーを作成し、それをIAMユーザー・グループに紐づけることで実現できます。制限できる内容としては、特定ブランチに対する git push や削除、マージの手法などです。

※参考

今回はシンプルに main ブランチへの直接Pushが実行できないようIAMポリシーを作成し、それを testgroup というIAMグループに付与することで、グループ内のIAMユーザーに操作制限を加えました。

IAMポリシーはjsonで書かれた例が多く見つかりましたが、今回はCloudFormationからYaml形式で書いた例を載せておきます。以下のようなYamlファイルを使ってリソースを作成しました。

Resources:
  CodeCommit:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: !Ref RepoName
      RepositoryDescription: test repo
  IAMPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties: 
      Description: prohibit direct push to codecommit
      Path: /
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Deny
            Action: 'codecommit:GitPush'
            Resource: '*'
            Condition:
              "StringEqualsIfExists":
                "codecommit:References": 
                  - "refs/heads/main"
              "Null":
                "codecommit:References": 
                  - "false"
  IAMUser:
    Type: AWS::IAM::User
    Properties:
      Groups:
        - !Ref IAMGroup
      UserName: !Ref UserName
      LoginProfile:
        Password: !Ref UserPassword
        PasswordResetRequired: "false"
  IAMGroup:
    Type: AWS::IAM::Group
    Properties:
      GroupName: !Ref GroupName
      ManagedPolicyArns:
        - !Ref IAMPolicy
        - "arn:aws:iam::aws:policy/AWSCodeCommitPowerUser"

※参考

承認ルールテンプレート

承認ルールテンプレートはCodeCommitの機能として備わっており、マネジメントコンソールから作成・管理することができます。

また承認ルールテンプレートの作成は、公式には提供されていないものの、CloudFormationの拡張機能を利用し、コミュニティの公開するパッケージを利用することで、CloudFormationテンプレートからの作成が可能になります。

今回はあまり紹介する例の見当たらなかった、CloudFormationのほうで作ってみました。登録するリソースタイプは Community::CodeCommit::ApprovalRuleTemplate Community::CodeCommit::RepositoryAssociation の2つです。

まずCloudFormationを利用する準備をするため、実行用のロールの作成とCloudFormationタイプの登録を行います。

# IAMロールの作成
$ aws cloudformation create-stack \
   --template-url https://community-resource-provider-catalog.s3.amazonaws.com/community-codecommit-approvalruletemplate-resource-role-0.1.0.yml \
   --stack-name community-codecommit-approvalruletemplate-resource-role \
   --capabilities CAPABILITY_IAM

$ aws cloudformation create-stack \
   --template-url https://community-resource-provider-catalog.s3.amazonaws.com/community-codecommit-repositoryassociation-resource-role-0.1.0.yml \
  --stack-name community-codecommit-repositoryassociation-resource-role \
   --capabilities CAPABILITY_IAM



# CloudFormationタイプの登録
$ aws cloudformation register-type \
   --region "ap-northeast-1" \
   --type-name "Community::CodeCommit::ApprovalRuleTemplate" \
   --schema-handler-package "s3://community-resource-provider-catalog/community-codecommit-approvalruletemplate-0.1.0.zip" \
   --type RESOURCE \
   --execution-role-arn <ApprovalRuleTemplate用のロールARNを指定>

$ aws cloudformation register-type \
   --region "ap-northeast-1" \
   --type-name "Community::CodeCommit::RepositoryAssociation" \
   --schema-handler-package "s3://community-resource-provider-catalog/community-codecommit-repositoryassociation-0.1.0.zip" \
   --type RESOURCE \
   --execution-role-arn <RepositoryAssociation用のロールARNを指定>

タイプ登録後は以下のようにコマンドを実行し、登録の進行状況を確認できます。 aws cloudformation register-type コマンド実行後に RegistrationTokenという出力があるので、そこに表示されたトークンを指定します。

$ aws cloudformation describe-type-registration --registration-token <RegistrationToken>
{
    "ProgressStatus": "IN_PROGRESS",
    "Description": "Deployment is currently in DEPLOY_STAGE of status IN_PROGRESS; ",
    "TypeArn": "arn:aws:cloudformation:ap-northeast-1:111111111111:type/resource/Community-CodeCommit-ApprovalRuleTemplate",
    "TypeVersionArn": "arn:aws:cloudformation:ap-northeast-1:111111111111:type/resource/Community-CodeCommit-ApprovalRuleTemplate/00000005"
}
$ aws cloudformation describe-type-registration --registration-token <RegistrationToken>
{
    "ProgressStatus": "COMPLETE",
    "Description": "Deployment is currently in DEPLOY_STAGE of status COMPLETED; ",
    "TypeArn": "arn:aws:cloudformation:ap-northeast-1:111111111111:type/resource/Community-CodeCommit-ApprovalRuleTemplate",
    "TypeVersionArn": "arn:aws:cloudformation:ap-northeast-1:111111111111:type/resource/Community-CodeCommit-ApprovalRuleTemplate/00000005"
}

なお、CloudFormationタイプはバージョン情報を含んでおり、何度か登録を繰り返すとバージョンが更新されます。ただしデフォルトで利用するタイプを明示的に変更しないと、CloudFormationからリソースを作成する際に使われるバージョンが古いものになるため、バージョンの変更が必要な場合は、以下のようにバージョンの確認と更新を行います。

# バージョン一覧の取得
$ aws cloudformation list-type-versions --type RESOURCE --type-name "Community::CodeCommit::RepositoryAssociation"
{
    "TypeVersionSummaries": [
        {
            "Type": "RESOURCE",
            "TypeName": "Community::CodeCommit::RepositoryAssociation",
            "VersionId": "00000001",
            "IsDefaultVersion": true,
            "Arn": "arn:aws:cloudformation:ap-northeast-1:111111111111:type/resource/Community-CodeCommit-RepositoryAssociation/00000001",
            "TimeCreated": "2022-01-06T15:02:17.280000+00:00",
            "Description": "Resource that allows for the association of a particular approval rule template to CodeCommit repositories."
        },

(中略)

        {
            "Type": "RESOURCE",
            "TypeName": "Community::CodeCommit::RepositoryAssociation",
            "VersionId": "00000005",
            "IsDefaultVersion": false,
            "Arn": "arn:aws:cloudformation:ap-northeast-1:111111111111:type/resource/Community-CodeCommit-RepositoryAssociation/00000005",
            "TimeCreated": "2022-01-07T13:27:06.992000+00:00",
            "Description": "Resource that allows for the association of a particular approval rule template to CodeCommit repositories."
        }
    ]
}

# デフォルトバージョンの指定
$ aws cloudformation set-type-default-version \
   --version-id 00000005 \
   --type-name Community::CodeCommit::RepositoryAssociation \
   --type RESOURCE

これで承認ルールテンプレートをCloudFormationから作成できるようになったので、以下のようなファイルを使って作成します。

  RuleTemplate:
    Type: Community::CodeCommit::ApprovalRuleTemplate
    Properties:
      Name: !Ref RuleTemplateName
      Description: test rule
      Content:
        Version: "2018-11-08"
        DestinationReferences:
          - "refs/heads/main"
        Statements:
          - Type: "Approvers"
            NumberOfApprovalsNeeded: 1
            ApprovalPoolMembers:
              - "*"
  RepoAssociation:
    Type: Community::CodeCommit::RepositoryAssociation
    Properties:
      ApprovalRuleTemplateArn: !Ref RuleTemplate
      RepositoryNames: 
        - !GetAtt CodeCommit.Name

※参考

実際の利用

ここまでで必要なリソースの作成が完了したので、作成した testuser というIAMユーザーからCodeCommitへの操作を行います。

※リソースの作成に利用したYamlファイルはこちら。

sample-file.yaml

AWSTemplateFormatVersion: '2010-09-09'
Description: codecommit

Parameters:
  RepoName:
    Type: String
    Default: "testrepo"
  UserName:
    Type: String
    Default: "testuser"
  UserPassword:
    Type: String
    Default: "testuser@1234"
  GroupName:
    Type: String
    Default: "testgroup"
  RuleTemplateName:
    Type: String
    Default: "testrule"
  
Resources:
  CodeCommit:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: !Ref RepoName
      RepositoryDescription: test repo
  IAMPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties: 
      Description: prohibit direct push to codecommit
      Path: /
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Deny
            Action: 'codecommit:GitPush'
            Resource: '*'
            Condition:
              "StringEqualsIfExists":
                "codecommit:References": 
                  - "refs/heads/main"
              "Null":
                "codecommit:References": 
                  - "false"
  IAMUser:
    Type: AWS::IAM::User
    Properties:
      Groups:
        - !Ref IAMGroup
      UserName: !Ref UserName
      LoginProfile:
        Password: !Ref UserPassword
        PasswordResetRequired: "false"
  IAMGroup:
    Type: AWS::IAM::Group
    Properties:
      GroupName: !Ref GroupName
      ManagedPolicyArns:
        - !Ref IAMPolicy
        - "arn:aws:iam::aws:policy/AWSCodeCommitPowerUser"
  RuleTemplate:
    Type: Community::CodeCommit::ApprovalRuleTemplate
    Properties:
      Name: !Ref RuleTemplateName
      Description: test rule
      Content:
        Version: "2018-11-08"
        DestinationReferences:
          - "refs/heads/main"
        Statements:
          - Type: "Approvers"
            NumberOfApprovalsNeeded: 1
            ApprovalPoolMembers:
              - "*"
  RepoAssociation:
    Type: Community::CodeCommit::RepositoryAssociation
    Properties:
      ApprovalRuleTemplateArn: !Ref RuleTemplate
      RepositoryNames: 
        - !GetAtt CodeCommit.Name

まずはIAMユーザー画面に移動し、CodeCommitへのアクセス情報を取得します。次にCodeCommitで利用するファイルを適当に用意します。

f:id:FY0323:20220109165300p:plain

ローカルの環境から testuser を使ってCodeCommitリポジトリの取得を行います。

$ git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/testrepo
Cloning into 'testrepo'...
Username for 'https://git-codecommit.ap-northeast-1.amazonaws.com': testuser-at-111111111111
Password for 'https://testuser-at-111111111111@git-codecommit.ap-northeast-1.amazonaws.com':
remote: Counting objects: 3, done.
Unpacking objects: 100% (3/3), done.

$ cd testrepo/
$ git branch
* main

ここで README.md を適当に編集し、 main ブランチへ直接Pushをしてみます。するとエラーが発生し、 main への直接Pushができないことが確認できます。

$ vi README.md
$ cat README.md 
# testrepo

20220108

$ git add .
$ git commit -m "initial commit"
[main 9390f5b] initial commit
 1 file changed, 3 insertions(+), 1 deletion(-)
$ git push origin main
Username for 'https://git-codecommit.ap-northeast-1.amazonaws.com': testuser-at-111111111111
Password for 'https://testuser-at-111111111111@git-codecommit.ap-northeast-1.amazonaws.com': 
Counting objects: 3, done.
Writing objects: 100% (3/3), 262 bytes | 131.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/testrepo
 ! [remote rejected] main -> main (You don't have permission to push changes to this branch.)
error: failed to push some refs to 'https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/testrepo'

ここでは test-branch というブランチから、変更をコミットします。

$ git checkout -b test-branch
Switched to a new branch 'test-branch'
$ git push origin test-branch 
Username for 'https://git-codecommit.ap-northeast-1.amazonaws.com': testuser-at-111111111111
Password for 'https://testuser-at-111111111111@git-codecommit.ap-northeast-1.amazonaws.com': 
Counting objects: 3, done.
Writing objects: 100% (3/3), 262 bytes | 131.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/testrepo
 * [new branch]      test-branch -> test-branch

CodeCommitの画面に移動し、Pull Requestを作成します。すると承認ルールテンプレートに記載した内容の通り、1件の承認がないとマージができないよう表示されます。

f:id:FY0323:20220109165322p:plain

ここで別のユーザーで再ログインし、承認を行うことで、マージが可能になります。

f:id:FY0323:20220109165755p:plain

f:id:FY0323:20220109165807p:plain

※参考

ArgoCD Projectの使い方を整理する

今回はArgoCDでマルチテナント向けに利用できる Project という機能について整理しようと思います。

ここ最近のArgoCDのアップデートにもProjectに関するものが含まれていたので、そちらも試しています。

Projectとは

ArgoCDでは、Applicationというカスタムリソースの中にGitリポジトリやデプロイ先のクラスター・Namespaceなどの情報を設定します。Projectは、このApplicationを論理的にグループ分けするカスタムリソースです。

ArgoCDでは、複数のチームやプロジェクトで同じArgoCDクラスターを利用する、いわゆるマルチテナントと呼ばれる利用方法を、デフォルトでサポートしています。ArgoCDアカウントとSSOとの統合やRBACの利用などがそれにあたりますが、Projectもその一つに含まれます。

ArgoCDを複数チームが利用する場合、それぞれのチームが利用するApplicationなどのリソースは異なることが多いでしょう。あるチームが別チームのリソースにアクセスできてしまうと、誤ったアプリケーションをデプロイしたり、別のクラスターやNamespaceにデプロイしてしまうような事故にもつながるため、チームごとに利用できるリソースを制限したり条件付けをしたくなります。ArgoCDではProjectを利用することで、それを実現します。

Projectでは、以下のような設定を行い、利用するリソースの制限をすることができます。

なお、ArgoCDはクラスター作成時点でデフォルトのProject ( default ) が作成されます。Application作成時にProjectを指定しないと、デフォルトのProjectと紐づくため、通常Applicationを作成する場合は全てこの default Projectに分類されます。 default はGitリポジトリクラスター・Namespace、リソースの種類に制限がなく、全ての操作が可能です。

Projectの基本的な使い方

ここからProjectの利用方法をなぞってみます。Projectを利用する場合、ユーザーとProjectとをRBACのポリシーで紐づけます。Projectが利用できるリソースの条件やポリシーを設定し、 argocd-rbac-cm というConfigMapでユーザーとProjectとを紐づけることで、ユーザーがそのProjectを操作できるようになります。

※Project-Account-RBACの概要図

f:id:FY0323:20211230212556p:plain

今回はローカルユーザーを作成し、そのユーザーが一部操作できるようなProjectを作成し、権限を付与して操作できるまでを試します。

ArgoCDはインストール済み、 admin ユーザーでArgoCDにログインされている前提で進めます。

ローカルユーザーの作成

まずはローカルユーザーを作成します。ローカルユーザーは argocd-cm ConfigMapに追加することで作成できます。ここでは dev1 というアカウントを作成します。

$ kubectl edit cm argocd-cm -n argocd

~~~
# 以下のようにアカウントを追加

data:
  accounts.dev1: login
~~~

configmap/argocd-cm edited


# 作成後のアカウントの状態
$ argocd account list
NAME   ENABLED  CAPABILITIES
admin  true     login
dev1   true     login

$ kubectl describe cm argocd-cm -n argocd
Name:         argocd-cm
Namespace:    argocd
Labels:       app.kubernetes.io/name=argocd-cm
              app.kubernetes.io/part-of=argocd
Annotations:  <none>

Data
====
accounts.dev1:
----
login
Events:  <none>

アカウント作成後はパスワードを設定します。

$ argocd account update-password \
> --account dev1 \
> --current-password <adminユーザーのパスワード> \
> --new-password <dev1に設定するパスワード>
Password updated


# 設定するパスワードが条件を満たしてないと以下のようなエラーが出力される

$ argocd account update-password \
> --account dev1 \
> --current-password <adminユーザーのパスワード> \
> --new-password <dev1に設定するパスワード>
FATA[0001] rpc error: code = Unknown desc = New password does not match the following expression: ^.{8,32}$. 

上記までで新規アカウントが作成され、ArgoCDにログインできるようになります。

# adminアカウントからログアウトする
$ argocd logout a9ed881bfa2bd42248dca9000f88f583-776281281.ap-northeast-1.elb.amazonaws.com:443

Logged out from 'a9ed881bfa2bd42248dca9000f88f583-776281281.ap-northeast-1.elb.amazonaws.com:443'


# dev1アカウントでログインする
$ argocd login a9ed881bfa2bd42248dca9000f88f583-776281281.ap-northeast-1.elb.amazonaws.com:443 

WARNING: server certificate had error: x509: certificate is valid for localhost, argocd-server, argocd-server.argocd, argocd-server.argocd.svc, argocd-server.argocd.svc.cluster.local, not a9ed881bfa2bd42248dca9000f88f583-776281281.ap-northeast-1.elb.amazonaws.com. Proceed insecurely (y/n)? y

Username: dev1
Password: <dev1パスワードを入力>
'dev1:login' logged in successfully

Context 'a9ed881bfa2bd42248dca9000f88f583-776281281.ap-northeast-1.elb.amazonaws.com:443' updated

試しにArgoCD UIにアクセスしてみると、アカウントも登録されていることが確認できます。

f:id:FY0323:20211230212828p:plain

※参考:

Projectの作成

次にProjectを作成します。Projectは AppProject というカスタムリソースで定義するか、 argocd proj create コマンドで作成できます。今回利用したyamlファイルはこちらです。

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: dev1
  namespace: argocd
spec:
  description: dev1 project
  sourceRepos:
    - '*'
  destinations:
    - namespace: '*'
      server: '*'
  clusterResourceWhitelist:
    - group: '*'
      kind: '*'
  roles:
    - name: dev1
      description: dev1 role for dev1 project
      policies:
        - p, proj:dev1:dev1, applications, get, dev1/*, allow

AppProjectspec 配下でどんなパラメータが利用できるか記載します。


  • description : Projectに関する記述
  • sourceRepos : 利用を許可するGitリポジトリを指定
  • destinations : 利用できるデプロイ先を指定
    • namespace : Namespaceの指定
    • server : クラスターURLの指定
  • clusterResourceBlacklist : 作成を禁止するcluster-scope (StorageClass CustomResourceDefinition ClusterRole など) のリソースを指定
    • group : Groupの指定
    • kind : Kindの指定
  • clusterResourceWhitelist : 作成を許可するcluster-scopeのリソースを指定
    • group
    • kind
  • namespaceResourceBlacklist : 作成を禁止するnamescope-scope (Pod Service ResourceQuota など)のリソースを指定
    • group
    • kind
  • namespaceResourceWhitelist : 作成を許可するnamescope-scopeのリソースを指定
    • group
    • kind
  • orphanedResources : ArgoCD applicationに属さないリソースのモニタリングを有効化するとき、warningメッセージを出すか否か
    • warn : true / false を指定
  • roles : Projectと紐づけるRBACロールを指定
    • name : Role名
    • description : Roleの説明
    • policies : ポリシーの記載
    • groups : Roleと紐づけるOIDCグループ
    • jwtTokens : Roleと紐づけるJWTトーク
  • signatureKeys : Gitコミットに対してSyncを許可するためのPGP keyのリスト
    • keyID : 16進数で示したキーID
  • syncWindows : Project内でSyncを実行するタイミングを制御する
    • applications : アプライするApplicationのリスト
    • clusters : アプライするクラスターのリスト
    • duration : Syncを許可する有効期間を指定
    • kind : Syncを許可するか禁止するかを指定。allow / deny を選択
    • manualSync : 手動Syncを許可するか否か。 true false を選択
    • namespaces : アプライするnamespaceのリスト
    • schedule : Cron形式でSyncを開始するスケジュールを指定
    • timeZone : TimeZoneの指定

このうち spec.roles.policies は Casbin-format形式でポリシーを定義します。ポリシーの記載は以下のようなルールで行います。

p, <role/user/group>, <resource>, <action>, <object>, allow

resource action で利用できるのは、以下の通りです。

  • resource: clusters projects applications repositories certificates accounts gpgkeys
  • action: get create update delete sync override action

例えば proj-name というProjectにある role-name というroleに対し、同じProject内の Application へのアクセスを許可する場合は、以下のように設定します。

spec:
  roles:
    - name: test-role
      policies:
        - p, proj:proj-name:role-name, applications, get, proj-name/*, allow

なお、後ほど出てきますが、AppProject でポリシーを指定する場合、resource で利用できるのは applications だけのようです。Projectで利用するポリシーは argocd-rbac-cm ConfigMapでも指定できるのですが、そちらでは repositories など別の resource も指定できます。

ここで上記 AppProject の定義ファイルをデプロイします。なお、デプロイ前には default というProjectが既にあることも確認できます。

# adminアカウントでArgoCDにログインし直す
$ argocd login a9ed881bfa2bd42248dca9000f88f583-776281281.ap-northeast-1.elb.amazonaws.com:443

# default Projectの確認
$ argocd proj list
NAME     DESCRIPTION  DESTINATIONS  SOURCES  CLUSTER-RESOURCE-WHITELIST  NAMESPACE-RESOURCE-BLACKLIST  SIGNATURE-KEYS  ORPHANED-RESOURCES
default               *,*           *        */*                         <none>                        <none>          disabled

# dev1 Projectの作成
$ kubectl apply -f proj-dev1.yaml 
appproject.argoproj.io/dev1 created

# 作成後の確認
$ argocd proj list
NAME     DESCRIPTION   DESTINATIONS  SOURCES  CLUSTER-RESOURCE-WHITELIST  NAMESPACE-RESOURCE-BLACKLIST  SIGNATURE-KEYS  ORPHANED-RESOURCES
default                *,*           *        */*                         <none>                        <none>          disabled
dev1     dev1 project  *,*           *        */*                         <none>                        <none>          disabled

$ argocd proj get dev1
Name:                        dev1
Description:                 dev1 project
Destinations:                *,*
Repositories:                *
Allowed Cluster Resources:   */*
Denied Namespaced Resources: <none>
Signature keys:              <none>
Orphaned Resources:          disabled

# dev1 ProjectのRoleを確認
$ argocd proj role list dev1
ROLE-NAME  DESCRIPTION
dev1       dev1 role for dev1 project

RBACの設定

次にRBACの設定を行うため、 argocd-rbac-cm ConfigMapを修正します。ここでアカウントとRoleを紐づけることで、アカウントからProjectを操作することが可能になります。

argocd-rbac-cm では policy.csv というdataを指定し、そこに必要なポリシーを定義します。ここでは先ほどProjectで設定したRBACポリシーとアカウントとを紐づける設定を行います。アカウントとポリシーとを紐づける場合は、以下のようなルールで定義します。

g, <user/group>, <role>

なお、ArgoCDはデフォルトで readonly admin というポリシーと admin アカウントが用意されています。ポリシーの内容は builtin-policy.csvで定義されており、 adminアカウントは全てのリソースに対する権限を持っています。

$ kubectl edit cm argocd-rbac-cm -n argocd

~~~
(中略)

data:
  policy.csv: |
    g, dev1, proj:dev1:dev1
~~~

configmap/argocd-rbac-cm edited

アクセスのテスト

ここまでで dev1 アカウントから dev1 Project中のApplicationにアクセスできる設定が完了しました。ここで dev1 Projectにテスト用のApplicationを作成してみます。

admin アカウントでArgoCD UIにログインし、テスト用Applicationを作成します。

f:id:FY0323:20211230214343p:plain

次に dev1 アカウントでUIにログインすると、上記Applicationは画面上に確認できます。一方で、上記ApplicationをSync / deleteなどしようとすると、権限が足りないというメッセージが表示されます。

f:id:FY0323:20211230214510p:plain

f:id:FY0323:20211230214607p:plain

dev1 Projectに新しいApplicationを作成しようとしても、同じくエラーメッセージが出力されます。

f:id:FY0323:20211230214727p:plain

またProjectの画面を見ると、 dev1 Projectのみが表示されており、 default Projectは確認ができません。

f:id:FY0323:20211230214838p:plain

一応CLIからも試すと、同様にエラーメッセージが出力されます。

# dev1アカウントでログイン
$ argocd login a9ed881bfa2bd42248dca9000f88f583-776281281.ap-northeast-1.elb.amazonaws.com:443

# Applicationは確認できる
$ argocd app list
NAME             CLUSTER                         NAMESPACE  PROJECT  STATUS  HEALTH   SYNCPOLICY  CONDITIONS  REPO
     PATH       TARGET
guestbook-admin  https://kubernetes.default.svc  default    dev1     Synced  Healthy  <none>      <none>      https://github.com/argoproj/argocd-example-apps.git  guestbook  HEAD

# sync / deleteはできない
$ argocd app sync guestbook-admin
FATA[0000] rpc error: code = PermissionDenied desc = permission denied: applications, sync, dev1/guestbook-admin, sub: dev1, iat: 2021-12-29T04:08:03Z 

$ argocd app delete guestbook-admin
Are you sure you want to delete 'guestbook-admin' and all its resources? [y/n]
y
FATA[0002] rpc error: code = PermissionDenied desc = permission denied: applications, delete, dev1/guestbook-admin, sub: dev1, iat: 2021-12-29T04:08:03Z 

# dev1 Projectしか確認できない
$ argocd proj list
NAME  DESCRIPTION   DESTINATIONS  SOURCES  CLUSTER-RESOURCE-WHITELIST  NAMESPACE-RESOURCE-BLACKLIST  SIGNATURE-KEYS  ORPHANED-RESOURCES
dev1  dev1 project  *,*           *        */*                         <none>                        <none>          disabled

簡単ですが、ここまででProjectの基本的な利用方法を紹介しました。

※参考:

Projectに関連する機能

ここからProjectと関連する他の機能を紹介します。

Orphaned Resource Monitoring

Orphaned resource monitoringは、ArgoCD Applicationで管理されないリソースを検知し、ArgoCD UIからそのリソースを確認・削除する機能を提供します。Orphaned resource monitoringを有効化するかは AppProject の定義で制御することができます。

ここでは先ほどの dev1 Projectの定義に、以下のようにOrphaned resourceの設定を追加します。

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: dev1
  namespace: argocd
spec:
 
(中略)
 
  orphanedResources:
    warn: true

設定後に再度デプロイすると、UIからApplicationでの管理外リソースとして kubernetes EndpointSliceを検知します。

$ kubectl apply -f proj-dev1.yaml 
appproject.argoproj.io/dev1 configured

$ argocd proj get dev1
Name:                        dev1
Description:                 dev1 project
Destinations:                *,*
Repositories:                *
Allowed Cluster Resources:   */*
Denied Namespaced Resources: <none>
Signature keys:              <none>
Orphaned Resources:          enabled (warn=true)

$ kubectl get endpoints
NAME           ENDPOINTS                            AGE
guestbook-ui   192.168.2.161:80                     64m
kubernetes     192.168.0.202:443,192.168.2.65:443   3h2m

f:id:FY0323:20211230215459p:plain

※参考:

Global Project

Global Projectは ver 1.8で追加された機能で、他のProjectに同じ設定を引き継ぐことができます。

ここではまず以下のようにGlobal Projectのソースとなる global Projectを作成します。 global には sourceRepos destinations の設定をしています。

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: global
  namespace: argocd
spec:
  description: global project
  sourceRepos:
    - '*'
  destinations:
    - namespace: 'argocd'
      server: '*'
# global Projectの作成
$ kubectl apply -f proj-global.yaml
appproject.argoproj.io/global created

$ argocd proj list
NAME     DESCRIPTION     DESTINATIONS  SOURCES  CLUSTER-RESOURCE-WHITELIST  NAMESPACE-RESOURCE-BLACKLIST  SIGNATURE-KEYS  ORPHANED-RESOURCES
default                  *,*           *        */*                         <none>                        <none>          disabled
dev1     dev1 project    *,*           *        */*                         <none>                        <none>          enabled (warn=true)
global   global project  *,argocd      *        <none>                      <none>                        <none>          disabled

次にGlobal Projectの設定を argocd-cm ConfigMapに追加します。 argocd-cm には、Global Projectの指定と、どのProjectに設定を引き継ぐかの条件を追加します。現在は Project にラベルを付与し、その設定から条件に合致するものを検索する形でのみ利用できるようです。

argocd-cm には以下のように data.globalProjects という設定を追加し、 matchExpressions にてラベルの条件を記載します。

$ kubectl edit cm argocd-cm -n argocd

~~~
(中略)
data:
  accounts.dev1: login
  globalProjects: |-
    - labelSelector:
      matchExpressions:
        - key: env
          operator: In
          values:
          - dev
      projectName: global
kind: ConfigMap
~~~

configmap/argocd-cm edited


# 追加後の設定内容
$ kubectl describe cm argocd-cm -n argocd
Name:         argocd-cm
Namespace:    argocd
Labels:       app.kubernetes.io/name=argocd-cm
              app.kubernetes.io/part-of=argocd
Annotations:  <none>

Data
====
accounts.dev1:
----
login
globalProjects:
----
- labelSelector:
  matchExpressions:
    - key: env
      operator: In
      values:
      - dev
  projectName: global
Events:  <none>

次に、条件に合致するような test Projectを作成します。こちらには clusterResourceWhitelist のみを設定しています。 global Projectから設定を引き継ぐことができれば、ここに sourceRepos destinations が追加されるはずです。

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: test
  namespace: argocd
  labels:
    env: dev
spec:
  description: test project
  clusterResourceWhitelist:
    - group: '*'
      kind: '*'

ここで上記Projectを作成してみますが、CLIからは特に設定が引き継がれていないように見えます。

# test Projectの作成
$ kubectl apply -f proj-test.yaml 
appproject.argoproj.io/test created


# 作成後の設定内容
$ argocd proj list
NAME     DESCRIPTION     DESTINATIONS  SOURCES  CLUSTER-RESOURCE-WHITELIST  NAMESPACE-RESOURCE-BLACKLIST  SIGNATURE-KEYS  ORPHANED-RESOURCES
default                  *,*           *        */*                         <none>                        <none>          disabled
dev1     dev1 project    *,*           *        */*                         <none>                        <none>          enabled (warn=true)
global   global project  *,argocd      *        <none>                      <none>                        <none>          disabled
test     test project    <none>        <none>   */*                         <none>                        <none>          disabled

$ kubectl describe appproject test -n argocd
Name:         test
Namespace:    argocd
Labels:       env=dev
Annotations:  <none>
API Version:  argoproj.io/v1alpha1
Kind:         AppProject
Metadata:
  Creation Timestamp:  2021-12-29T05:20:47Z
  Generation:          1
  Managed Fields:
    API Version:  argoproj.io/v1alpha1
    Fields Type:  FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .:
          f:kubectl.kubernetes.io/last-applied-configuration:
        f:labels:
          .:
          f:env:
      f:spec:
        .:
        f:clusterResourceWhitelist:
        f:description:
    Manager:         kubectl-client-side-apply
    Operation:       Update
    Time:            2021-12-29T05:20:47Z
  Resource Version:  28205
  UID:               5064245e-8823-4dd4-85c2-9cbbbb301c71
Spec:
  Cluster Resource Whitelist:
    Group:      *
    Kind:       *
  Description:  test project
Events:         <none>

一方でUIを確認すると、こちらでは以下のようにGlobal Projectの設定を引き継ぎ、 sourceRepos destinations の設定が追加されています。

f:id:FY0323:20211230220139p:plain

UIでは Inherited from Global Projects というパートもあり、こちらにはGlobal Projectの設定内容が確認できます。

f:id:FY0323:20211230220307p:plain

このあと作成した global-test ProjectでApplicationを作成しましたが、argocd namespaceを指定することで作成できることを確認できました。また argocd でなく default namespaceを指定するとエラーが出力されるのも確認できました。

$ argocd app list
NAME             CLUSTER                         NAMESPACE  PROJECT  STATUS  HEALTH   SYNCPOLICY  CONDITIONS               REPO
                  PATH       TARGET
guestbook-admin  https://kubernetes.default.svc  default    dev1     Synced  Healthy  <none>      OrphanedResourceWarning  https://github.com/argoproj/argocd-example-apps.git  guestbook  HEAD
guestbook-test   https://kubernetes.default.svc  argocd     test     Synced  Healthy  <none>      <none>                   https://github.com/argoproj/argocd-example-apps.git  guestbook  HEAD

※参考:

Project scoped Repositories & Clusters

Project scoped Repositories / Clustersは ver 2.2で追加された機能です。概要は以前触れましたが、Project単位で別れたチームに対し、リポジトリクラスターを追加する権限を付与するための機能になります。

ここでは argocd-rbac-cm ConfigMapにポリシーを追加することで、 dev1 アカウントがリポジトリを追加できるようにします。当初は先ほどと同様Projectの中にポリシーを追加しようとしたのですが、ProjectからはApplicationリソースに対するポリシーしか設定することができないようです。

こちらのIssueに対応策が書かれていたのですが、将来的にはProjectからも repositories など別のリソースに対するポリシーを設定できるようになるかもしれません。

まずは検証用に project-scoped Projectを作成します。

kind: AppProject
metadata:
  name: project-scoped
  namespace: argocd
spec:
  description: project-scoped project
  sourceRepos:
    - '*'
  destinations:
    - namespace: '*'
      server: '*'
  clusterResourceWhitelist:
    - group: '*'
      kind: '*'
  roles:
    - name: dev1-scope
      description: role for project-scoped repo
      policies:
        - p, proj:project-scoped:dev1-scope, applications, *, project-scoped/*, allow
# dev1 アカウントでログイン
$ argocd login a9ed881bfa2bd42248dca9000f88f583-776281281.ap-northeast-1.elb.amazonaws.com:443

# project-scoped Projectの作成
$ kubectl apply -f proj-scoped.yaml 
appproject.argoproj.io/project-scoped configured

# 作成後の確認
$ argocd proj list
NAME            DESCRIPTION             DESTINATIONS  SOURCES  CLUSTER-RESOURCE-WHITELIST  NAMESPACE-RESOURCE-BLACKLIST  SIGNATURE-KEYS  ORPHANED-RESOURCES
dev1            dev1 project            *,*           *        */*                         <none>                        <none>          enabled (warn=true)  
project-scoped  project-scoped project  *,*           *        */*                         <none>                        <none>          disabled

次にここでは以下のように argocd-rbac-cm ConfigMapに dev1-scope というポリシーを追加し、 dev1-scope ポリシーと dev1 アカウントを紐づけます。

$ kubectl edit cm argocd-rbac-cm -n argocd

~~~
(中略)
data:
  policy.csv: |
    g, dev1, proj:dev1:dev1
    p, role:dev1-scope, repositories, *, project-scoped/*, allow
    g, dev1, role:dev1-scope
~~~

configmap/argocd-rbac-cm edited

最後に project-scoped Projectを指定しながらリポジトリを追加します。なお argocd repo add コマンドで --project オプションが有効なのは ver 2.2以降なので、 argocd CLIのバージョンが古い場合はアップデートが必要です。

# リポジトリを追加
$ argocd repo add --name project-scoped https://github.com/argoproj/argocd-example-apps.git --project project-scoped
Repository 'https://github.com/argoproj/argocd-example-apps.git' added

$ argocd repo list
TYPE  NAME            REPO                                                 INSECURE  OCI    LFS    CREDS  STATUS      MESSAGE  PROJECT
git   project-scoped  https://github.com/argoproj/argocd-example-apps.git  false     false  false  false  Successful           project-scoped

※参考:

その他

Projectをどのように分割するかについては、具体的な事例も少なく、まだBest practiceのようなものは共有されていないようです。ArgoCDでApp-of-appsパターンを利用する場合のProjectについては、こちらの記事が参考になりそうです

※参考:

【メモ】AWS CodeBuildでECRへのアクセス情報を設定するときの選択肢

以前CodeCommit / CodeBuildで ECR / EKS向けのCI/CDパイプラインを作った際、CodeBuild中でECRへのアクセス情報を設定する方法がよくわかっていなかったので、備忘録として書き残しておきます。

各値の取得方法

Amazon ECRへコンテナイメージを格納するには、AWSアカウントID / リージョン / リポジトリ名の3つの値が必要となります。前提として、CodeBuildプロジェクトが作成されたのと同じ環境の情報を取得する場合を書きます。

アカウントID

アカウントを取得する場合は、例えばAWS CLIのコマンド出力をクエリするか、Arnの値から抜き出すことで取得できます。

$ aws sts get-caller-identity --query 'Account' --output text

$ echo ${CODEBUILD_BUILD_ARN} | cut -f 5 -d :

※参考:

リージョン

リージョンを利用する場合は、CodeBuildの環境変数である AWS_DEFAULT_REGION を指定することでリージョン名を取得できます。

※参考

ECRリポジトリ

リポジトリが1つしかない場合は、 aws ecr describe-repositories コマンドの出力からクエリするのが簡単そうです。またこの方法だとリポジトリURIを取得できるので、上記2つの値を取得する必要もなくなります。

$ aws ecr describe-repositories --query 'repositories[0].repositoryName' --output text       
test-ecr

$ aws ecr describe-repositories --query 'repositories[0].repositoryUri' --output text        
000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/test-ecr

リポジトリが複数ある場合、上記コマンドのリストの指定を変更するか、パラメータで直接指定する方法になりそうです。

また、ECRをCloudFormationで作成している場合は、ECR作成時にRepositoryUri を出力することで、CodeBuildから指定することができます。

※参考

別アカウントの場合

別アカウントの情報を簡単に取得することは基本的にできなさそうなので、パラメータで直接指定することになりそうです。

StackSetsを使ってのクロスアカウントなリソース作成後にリソースを参照できたりするんでしょうか。

パラメータでの指定方法

CodeBuildではいくつかパラメータの指定方法があるので、そちらも書いておきます。

buildspec.yaml 中で指定

buildspec.yaml では env にて変数を指定することができます。また指定方法は3種類あり、それぞれ variables parameter-store secrets-manager になります。

variables はkey: value形式で変数を指定するシンプルな形式である一方、 parameter-store はSystems Manager Parameter Storeに格納した情報を、 secrets-manager はSecrets Managerに格納した情報を利用することができます。

※参考:

CodeBuildの EnvironmentVariables

CodeBuildの設定でパラメータを指定することもできます。 EnvironmentVariables にkey: value形式で指定することで、 buildspec.yaml に変数を渡すことができます。

またCloudFormationの Parameter の値を EnvironmentVariables に渡して利用することもできます。

AWSTemplateFormatVersion: "2010-09-09"
Description: CodeBuild
 
Parameters:
  ecrRepo:
    Type: String
    Default: "test-ecr"
Resources:
  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: codebuild-test
      Description: Test project for AWS CI/CD
      ServiceRole: !GetAtt BuildRole.Arn
      Artifacts:
        Type: NO_ARTIFACTS
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/docker:18.09.0
        EnvironmentVariables:
        - Name: "ECR_REPO"
          Value: !Ref ecrRepo

ArgoCDの軽量ディストリビューション ArgoCD Coreを動かしてみる

今回はArgoCD CoreというArgoCDの新たなディストリビューションを動かしてみました。

ArgoCD Coreとは

ArgoCD Coreは ArgoCDの ver 2.1で導入された、ArgoCD distributionになります。ArgoCDはデフォルトだとSSO / RBAC / ユーザー管理などの機能を搭載しており、1つのArgoCDインスタンスを複数のチームが利用できる、マルチテナントをサポートしています。

一方でArgoCDを利用するユーザーの中には、マルチテナントをサポートする機能が不要であり、ArgoCDの提供するGitOps的な機能だけを使いたい人も大勢います。そういったユーザー向けに登場したのがArgoCD Coreです。

ArgoCD Coreでは、デフォルトのArgoCDディストリビューションから一部機能を削減し、管理者にとってよりシンプルに利用できるようになっています。特徴は以下の通りです。

  • ArgoCDにアクセスする際、アカウント・パスワード情報を入力しなくてよい
  • ArgoCDにアクセスするためにAPIを外部に公開する必要はない

ArgoCD Coreを動かす

ここからArgoCD Coreを動かします。環境は以下の通り。

インストールはいつもの通り、namespaceの作成とArgoCD関連リソースのデプロイを行います。

$ kubectl create namespace argocd

$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v2.1.7/manifests/core-install.yaml

デプロイ後は以下の通り。デフォルトと比べると、Dex ServerやArgoCD Serverが無かったり、一部コンポーネントが削減されている様子が見えます。

$ kubectl get pods -n argocd
NAME                                  READY   STATUS    RESTARTS   AGE
argocd-application-controller-0       1/1     Running   0          60s
argocd-redis-5b6967fdfc-2prrf         1/1     Running   0          60s
argocd-repo-server-7598bf5999-brjvv   1/1     Running   0          60s

$ kubectl get svc -n argocd
NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE
argocd-metrics       ClusterIP   10.100.41.200    <none>        8082/TCP            80s
argocd-redis         ClusterIP   10.100.80.99     <none>        6379/TCP            80s
argocd-repo-server   ClusterIP   10.100.204.189   <none>        8081/TCP,8084/TCP   80s

$ kubectl get cm -n argocd
NAME                        DATA   AGE
argocd-cm                   0      90s
argocd-cmd-params-cm        0      90s
argocd-gpg-keys-cm          0      90s
argocd-rbac-cm              0      90s
argocd-ssh-known-hosts-cm   1      90s
argocd-tls-certs-cm         0      90s
kube-root-ca.crt            1      2m18s

$ kubectl get secret -n argocd
NAME                                        TYPE                                  DATA   AGE
argocd-application-controller-token-7sk75   kubernetes.io/service-account-token   3      99s
argocd-redis-token-9rggh                    kubernetes.io/service-account-token   3      99s
argocd-secret                               Opaque                                0      98s
default-token-zsthn                         kubernetes.io/service-account-token   3      2m26s

ここからArgoCD Coreを操作してみます。ArgoCDを操作するためにまずはログインをします。ArgoCD CoreではAPIを公開せずとも argocd login –core と実行するだけでログインが完了できるので、初期セットアップの手間がかなり省けます。

$ argocd login --core
Context 'kubernetes' updated

ArgoCD Coreを操作するには argocd CLIを利用しますが、Coreを利用する場合は作業前に操作するNamespaceを argocd に変更する必要があります。変更しないと argocd-cm を見つけられず、 argocd CLIが実行できないようです。

# Namespace変更前
$ argocd version
FATA[0000] configmap "argocd-cm" not found

$ argocd app list
FATA[0000] configmap "argocd-cm" not found


# 変更後
$ kubectl config set-context --current --namespace=argocd
Context "testuser@eks-cluster.ap-northeast-1.eksctl.io" modified.

$ argocd version
argocd: v2.1.6+a346cf9
  BuildDate: 2021-10-28T19:59:40Z
  GitCommit: a346cf933e10d872eae26bff8e58c5e7ac40db25
  GitTreeState: clean
  GoVersion: go1.16.5
  Compiler: gc
  Platform: linux/amd64
argocd-server: v2.1.6+a346cf9
  BuildDate: 2021-10-28T19:59:40Z
  GitCommit: a346cf933e10d872eae26bff8e58c5e7ac40db25
  GitTreeState: clean
  GoVersion: go1.16.5
  Compiler: gc
  Platform: linux/amd64
  Ksonnet Version: unable to determine ksonnet version: exec: "ks": executable file not found in $PATH
  Kustomize Version: could not get kustomize version: exec: "kustomize": executable file not found in $PATH
  Helm Version: v3.4.2+g23dd3af
  Kubectl Version: v0.21.0
  Jsonnet Version: v0.17.0

$ argocd app list
NAME  CLUSTER  NAMESPACE  PROJECT  STATUS  HEALTH  SYNCPOLICY  CONDITIONS  REPO  PATH  TARGET

ArgoCD UIへアクセスをする場合は argocd admin dashboard を実行するとポートフォワーディングされ、 http://localhost:8080 へアクセスすることでUIが表示されます。

$ argocd admin dashboard
Argo CD UI is available at http://localhost:8080

UIは以下の通りです。ぱっと見は通常のArgoCDとほぼ変わらないように見えますが、ログインはしておらずunknown userとして認識されます。

f:id:FY0323:20211218195851p:plain

Projectも利用できますが、マルチテナント向けの機能なので、いずれなくなるんじゃないかと思ってます。

f:id:FY0323:20211218195906p:plain

f:id:FY0323:20211218195920p:plain

もちろんUIからアプリケーションを登録・デプロイすることもできます。

f:id:FY0323:20211218195929p:plain

感想

今回はArgoCD Coreを動かしてみました。軽く触った感じでは、これまでArgoCDを使う上でちょっと煩わしい部分を省きつつ、ArgoCDで実現したいGitOpsの機能を使うこともできるように感じたので、これからArgoCDを試す場合や小規模なチーム・プロジェクトなどで使う分には十分ではないかと感じました。気になる方は使い始めてみるのもよいかもしれません。

参考リンク

【メモ】AWS CodeCommit / CodeBuildと ECR / EKSを利用したCI/CDパイプラインの例

はじめに

今回は AWS CodeCommit / CodeBuildを使い、コンテナイメージのビルドからKubernetesクラスターへのデプロイまで実行するサンプルを作成しました。

CodeCommit / CodeBuildは、それ単体では実現できることがかなり限られるため、Amazon EventBridgeやAWS Chatbotなどのサービスも組み合わせております。また、Lambdaを使えばもっと効率的なパイプライン構成が作れるかもしれませんが、今回はLambdaを使わずに作っています。

構成

今回のパイプラインは、以下のサービスで構成します。

サービス名 リソース 個数
AWS CodeCommit Repository 2
AWS CodeBuild Project 4
AWS EventBridge Rule 4
AWS Chatbot Slack Channel Configuration 1
AWS CodeStar Notifications 4

パイプラインは以下のような流れで推移します。

  • CodeCommitリポジトリは、アプリケーション用・Kubernetesマニフェスト用に分ける
  • アプリケーション用リポジトリmaster ブランチに対するPull Requestが作成されると、アプリケーションのテストコードが実行される。Pull Requestがマージされると、コンテナイメージのビルドが実行され、ECRに格納される。
  • マニフェストリポジトリmaster ブランチに対するPull Requestが作成されると、マニフェストファイルのValidationが実行される。マージされると、EKSクラスターへのデプロイが実行される。
  • CodeBuildのすべての実行結果は、Chatbotを経由してSlackのチャンネルに通知される。

パイプラインの構成図は以下の通りです。本当は一つのCodeCommitリポジトリに全てのコードを置きたかったのですが、リポジトリ中のフォルダごとに実行するCodeBuildプロジェクトを分ける方法が見当たらなかったため、リポジトリを2つ用意しています。

f:id:FY0323:20211228111142p:plain

f:id:FY0323:20211228111156p:plain

CodeCommit中のフォルダ構成は以下の通りです。使用したコードは以前のこちらを流用しています

# アプリケーション用
.
├── README.md
└── app
    ├── Dockerfile
    ├── main.go
    └── main_test.go

# マニフェスト用
.
├── README.md
└── manifest
    └── deployment.yaml

Dockerfile

FROM golang:1.15.6 as builder
 
WORKDIR /go/src
 
COPY ./main.go ./
ARG CGO_ENABLED=0
ARG GOOS=linux
ARG GOARCH=amd64
RUN go build -o /go/bin/main
 
 
FROM scratch as runner
 
COPY --from=builder /go/bin/main /app/main
 
ENTRYPOINT ["/app/main"]

main.go

package main
 
import (
    "fmt"
    "net/http"
)
 
func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "<html><body>hello AWS CI/CD</body></html>\n")
}
 
func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

main_test.go

package main
 
import (
    "net/http"
    "net/http/httptest"
    "testing"
)
 
func TestHandler(t *testing.T) {
    testserver := httptest.NewServer(http.HandlerFunc(handler))
    defer testserver.Close()
 
    res, err := http.Get(testserver.URL)
    defer res.Body.Close()
 
    if err != nil {
        t.Fatalf("failed test %#v", err)
    }
    if res.StatusCode != 200 {
        t.Error("response code is not 200")
    }
}
 

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-app
  labels:
    app: sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sample
  template:
    metadata:
      labels:
        app: sample
    spec:
      containers:
      - name: app
        image: 000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/ecr-cicd:latest
        ports:
        - containerPort: 8080
 
---
apiVersion: v1
kind: Service
metadata:
  name: sample-app
spec:
  selector:
    app: sample
  ports:
  - protocol: TCP
    name: http
    port: 80
    targetPort: 8080

パイプラインを作成するCloudFormationファイルは以下の通りです。CodeBuildの buildspec.yaml にECRリポジトリの情報をうまく渡す方法が分からなかったため、EnvironmentVariables に値を設定する必要があります。

sample-resources.yaml

AWSTemplateFormatVersion: '2010-09-09'
Description: Sample resources for CI/CD
 
Parameters:
  NotifySlackChannel:
    Type: String
    Description: Slack Channel ID
    Default: ""
  NotifyChatbotWorkspaceId:
    Type: String
    Description: Chatbot Workspace ID
    Default: ""
 
Resources:
  ###########################
  # Sample resources for CI #
  ###########################
  # CodeCommit for CI
  CodeCommitForCI:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: codecommit-ci
      RepositoryDescription: CodeCommit repository for CI
  # CodeBuild + IAM for CI
  CodeBuildForCITest:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: codebuild-ci-test
      Description: CodeBuild project for CI test
      ServiceRole: !GetAtt BuildRoleForCITest.Arn
      Artifacts:
        Type: NO_ARTIFACTS
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
      Source:
        Location: !GetAtt CodeCommitForCI.CloneUrlHttp
        Type: CODECOMMIT
        BuildSpec: |
          version: 0.2
 
          phases:
            install:
              runtime-versions:
                golang: 1.14
            build:
              commands:
                - echo Test the Go code
                - go test -v $CODEBUILD_SRC_DIR/app
  BuildRoleForCITest:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: "Allow"
            Action: 'sts:AssumeRole'
            Principal:
              Service: codebuild.amazonaws.com
  BuildPoliciesForCITest:
    Type: "AWS::IAM::Policy"
    Properties:
      PolicyName: Policy-for-ci-test
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - logs:*
            Resource: '*'
            Effect: Allow
          - Resource: "*"
            Effect: Allow
            Action:
              - 'codebuild:*'
              - 'codecommit:*'
              - 'events:*'
              - 'logs:CreateLogGroup'
              - 'logs:CreateLogStream'
              - 'logs:PutLogEvents'
              - 'iam:*'
      Roles:
        - Ref: "BuildRoleForCITest"
 
  CodeBuildForCIBuild:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: codebuild-ci-build
      Description: CodeBuild project for CI build
      ServiceRole: !GetAtt BuildRoleForCIBuild.Arn
      Artifacts:
        Type: NO_ARTIFACTS
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/docker:18.09.0
        # please set the variables
        EnvironmentVariables:
        - Name: "AWS_ACCOUNT_ID"
          Value: "000000000000"
        - Name: "AWS_DEFAULT_REGION"
          Value: "ap-northeast-1"
        - Name: "ECR_REPO"
          Value: "ecr-cicd"
      Source:
        Location: !GetAtt CodeCommitForCI.CloneUrlHttp
        Type: CODECOMMIT
        BuildSpec: |
          version: 0.2
 
          phases:
            pre_build:
              commands:
                - echo Login to ECR
                - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION)
                - REPOSITORY=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$ECR_REPO
            build:
              commands:
                - echo Build Docker image
                - docker build -t $REPOSITORY:latest app/
            post_build:
              commands:
                - echo Push to ECR
                - docker push $REPOSITORY:latest
  BuildRoleForCIBuild:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: "Allow"
            Action: 'sts:AssumeRole'
            Principal:
              Service: codebuild.amazonaws.com
  BuildPoliciesForCIBuild:
    Type: "AWS::IAM::Policy"
    Properties:
      PolicyName: Policy-for-ci-build
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - logs:*
            Resource: '*'
            Effect: Allow
          - Resource: "*"
            Effect: Allow
            Action:
              - 'ecr:*'
              - 'codebuild:*'
              - 'codecommit:*'
              - 'events:*'
              - 'logs:CreateLogGroup'
              - 'logs:CreateLogStream'
              - 'logs:PutLogEvents'
              - 'iam:*'
      Roles:
        - Ref: "BuildRoleForCIBuild"
  # EventBridge for CI
  EventBridgeForCITest:
    Type: AWS::Events::Rule
    Properties:
      Description: EventBridge for CI test
      EventPattern:
        source:
          - "aws.codecommit"
        resources:
          - !GetAtt CodeCommitForCI.Arn
        detail-type:
          - "CodeCommit Pull Request State Change"
        detail:
          event:
            - "pullRequestCreated"
          pullRequestStatus:
            - "Open"
          destinationReference:
            - "refs/heads/master"
      Name: EventBridgeForCITest
      State: "ENABLED"
      Targets:
        - Arn: !GetAtt CodeBuildForCITest.Arn
          Id: EventBridgeForCITest
          RoleArn: !GetAtt EventBridgeRoleForCITest.Arn
  EventBridgeRoleForCITest:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: Policy-eventbridge-for-ci-test
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Resource: !GetAtt CodeBuildForCITest.Arn
                Action:
                  - codebuild:StartBuild
  EventBridgeForCIBuild:
    Type: AWS::Events::Rule
    Properties:
      Description: EventBridge for CI build
      EventPattern:
        source:
          - "aws.codecommit"
        resources:
          - !GetAtt CodeCommitForCI.Arn
        detail-type:
          - "CodeCommit Pull Request State Change"
        detail:
          event:
            - "pullRequestMergeStatusUpdated"
          pullRequestStatus:
            - "Closed"
          destinationReference:
            - "refs/heads/master"
      Name: EventBridgeForCIBuild
      State: "ENABLED"
      Targets:
        - Arn: !GetAtt CodeBuildForCIBuild.Arn
          Id: EventBridgeForCIBuild
          RoleArn: !GetAtt EventBridgeRoleForCIBuild.Arn
  EventBridgeRoleForCIBuild:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: Policy-eventbridge-for-ci-build
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Resource: !GetAtt CodeBuildForCIBuild.Arn
                Action:
                  - codebuild:StartBuild
  ###########################
  # Sample resources for CD #
  ###########################
  # CodeCommit for CD
  CodeCommitForCD:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: codecommit-cd
      RepositoryDescription: CodeCommit repository for CD
  # CodeBuild + IAM for CD
  CodeBuildForCDTest:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: codebuild-cd-test
      Description: CodeBuild project for CD test
      ServiceRole: !GetAtt BuildRoleForCDTest.Arn
      Artifacts:
        Type: NO_ARTIFACTS
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
      Source:
        Location: !GetAtt CodeCommitForCD.CloneUrlHttp
        Type: CODECOMMIT
        BuildSpec: |
          version: 0.2
 
          phases:
            install:
              commands:
                - echo Install AWS CLI v2
                - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
                - unzip awscliv2.zip > /dev/null
                - ls -l /root/.pyenv/shims/aws
                - ./aws/install --bin-dir /root/.pyenv/shims --install-dir /usr/local/aws-cli --update
                - aws --version
                - echo Install kubeval
                - wget -nv https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-linux-amd64.tar.gz
                - tar xf kubeval-linux-amd64.tar.gz
                - cp kubeval /usr/local/bin
                - kubeval --version
            build:
              commands:
                - echo Validate manifest files
                - kubeval $CODEBUILD_SRC_DIR/manifest/deployment.yaml
  BuildRoleForCDTest:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: "Allow"
            Action: 'sts:AssumeRole'
            Principal:
              Service: codebuild.amazonaws.com
  BuildPoliciesForCDTest:
    Type: "AWS::IAM::Policy"
    Properties:
      PolicyName: Policy-for-cd-test
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - logs:*
            Resource: '*'
            Effect: Allow
          - Resource: "*"
            Effect: Allow
            Action:
              - 'codebuild:*'
              - 'codecommit:*'
              - 'events:*'
              - 'logs:CreateLogGroup'
              - 'logs:CreateLogStream'
              - 'logs:PutLogEvents'
              - 'iam:*'
      Roles:
        - Ref: "BuildRoleForCDTest"
 
  CodeBuildForCDDeploy:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: codebuild-cd-deploy
      Description: CodeBuild project for CI deploy
      ServiceRole: !GetAtt BuildRoleForCDDeploy.Arn
      Artifacts:
        Type: NO_ARTIFACTS
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
      Source:
        Location: !GetAtt CodeCommitForCD.CloneUrlHttp
        Type: CODECOMMIT
        # change the parameters as necessary
        BuildSpec: |
          version: 0.2
 
          phases:
            install:
              commands:
                - echo Install AWSCLI ver.2
                - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
                - unzip awscliv2.zip > /dev/null
                - ls -l /root/.pyenv/shims/aws
                - ./aws/install --bin-dir /root/.pyenv/shims --install-dir /usr/local/aws-cli --update
                - aws --version
                - echo Install kubectl
                - curl -o kubectl https://amazon-eks.s3.us-west-2.amazonaws.com/1.21.2/2021-07-05/bin/linux/amd64/kubectl
                - chmod +x ./kubectl
                - mkdir -p $HOME/bin && cp ./kubectl $HOME/bin/kubectl && export PATH=$PATH:$HOME/bin
                - kubectl version --short --client
            build:
              commands:
                - echo Apply Kubernetes manifest
                - aws eks update-kubeconfig --name <EKS Cluster name>
                - kubectl apply -f $CODEBUILD_SRC_DIR/manifest
  BuildRoleForCDDeploy:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: "Allow"
            Action: 'sts:AssumeRole'
            Principal:
              Service: codebuild.amazonaws.com
      RoleName: "buildroleforcddeploy"
  BuildPoliciesForCDDeploy:
    Type: "AWS::IAM::Policy"
    Properties:
      PolicyName: Policy-for-cd-deploy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - logs:*
            Resource: '*'
            Effect: Allow
          - Resource: "*"
            Effect: Allow
            Action:
              - 'ecr:*'
              - 'codebuild:*'
              - 'codecommit:*'
              - 'events:*'
              - 'eks:*'
              - 'logs:CreateLogGroup'
              - 'logs:CreateLogStream'
              - 'logs:PutLogEvents'
              - 'iam:*'
      Roles:
        - Ref: "BuildRoleForCDDeploy"
  # EventBridge for CD
  EventBridgeForCDTest:
    Type: AWS::Events::Rule
    Properties:
      Description: EventBridge for CD test
      EventPattern:
        source:
          - "aws.codecommit"
        resources:
          - !GetAtt CodeCommitForCD.Arn
        detail-type:
          - "CodeCommit Pull Request State Change"
        detail:
          event:
            - "pullRequestCreated"
          pullRequestStatus:
            - "Open"
          destinationReference:
            - "refs/heads/master"
      Name: EventBridgeForCDTest
      State: "ENABLED"
      Targets:
        - Arn: !GetAtt CodeBuildForCDTest.Arn
          Id: EventBridgeForCDTest
          RoleArn: !GetAtt EventBridgeRoleForCDTest.Arn
  EventBridgeRoleForCDTest:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: Policy-eventbridge-for-cd-test
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Resource: !GetAtt CodeBuildForCDTest.Arn
                Action:
                  - codebuild:StartBuild
  EventBridgeForCDDeploy:
    Type: AWS::Events::Rule
    Properties:
      Description: EventBridge for CD deploy
      EventPattern:
        source:
          - "aws.codecommit"
        resources:
          - !GetAtt CodeCommitForCD.Arn
        detail-type:
          - "CodeCommit Pull Request State Change"
        detail:
          event:
            - "pullRequestMergeStatusUpdated"
          pullRequestStatus:
            - "Closed"
          destinationReference:
            - "refs/heads/master"
      Name: EventBridgeForCDDeploy
      State: "ENABLED"
      Targets:
        - Arn: !GetAtt CodeBuildForCDDeploy.Arn
          Id: EventBridgeForCDDeploy
          RoleArn: !GetAtt EventBridgeRoleForCDDeploy.Arn
  EventBridgeRoleForCDDeploy:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: Policy-eventbridge-for-cd-deploy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Resource: !GetAtt CodeBuildForCDDeploy.Arn
                Action:
                  - codebuild:StartBuild
 
  ###########################
  # Sample resources common #
  ###########################
  # ECR
  ECRForCICD:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: ecr-cicd
      ImageScanningConfiguration:
        ScanOnPush: "true"
  # Chatbot
  ChatbotSlackConfiguration:
    Type: AWS::Chatbot::SlackChannelConfiguration
    Properties:
      ConfigurationName: chatbot-slack-configuration
      IamRoleArn: !GetAtt ChatbotRole.Arn
      LoggingLevel: ERROR
      SlackChannelId: !Ref NotifySlackChannel
      SlackWorkspaceId: !Ref NotifyChatbotWorkspaceId
  ChatbotRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - chatbot.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/CloudWatchReadOnlyAccess
  # CodeStar Notification Rule
  NotificationRoleForCITest:
    Type: 'AWS::CodeStarNotifications::NotificationRule'
    Properties:
      Name: 'notifications for CI test'
      DetailType: FULL
      Resource: !GetAtt CodeBuildForCITest.Arn
      EventTypeIds:
        - codebuild-project-build-state-failed
        - codebuild-project-build-state-succeeded
      Targets:
        - TargetType: AWSChatbotSlack
          TargetAddress: !GetAtt ChatbotSlackConfiguration.Arn
  NotificationRoleForCIBuild:
    Type: 'AWS::CodeStarNotifications::NotificationRule'
    Properties:
      Name: 'notifications for PR build'
      DetailType: FULL
      Resource: !GetAtt CodeBuildForCIBuild.Arn
      EventTypeIds:
        - codebuild-project-build-state-failed
        - codebuild-project-build-state-succeeded
      Targets:
        - TargetType: AWSChatbotSlack
          TargetAddress: !GetAtt ChatbotSlackConfiguration.Arn
  NotificationRoleForCDTest:
    Type: 'AWS::CodeStarNotifications::NotificationRule'
    Properties:
      Name: 'notifications for CD test'
      DetailType: FULL
      Resource: !GetAtt CodeBuildForCDTest.Arn
      EventTypeIds:
        - codebuild-project-build-state-failed
        - codebuild-project-build-state-succeeded
      Targets:
        - TargetType: AWSChatbotSlack
          TargetAddress: !GetAtt ChatbotSlackConfiguration.Arn
  NotificationRoleForCDDeploy:
    Type: 'AWS::CodeStarNotifications::NotificationRule'
    Properties:
      Name: 'notifications for CD Deploy'
      DetailType: FULL
      Resource: !GetAtt CodeBuildForCDDeploy.Arn
      EventTypeIds:
        - codebuild-project-build-state-failed
        - codebuild-project-build-state-succeeded
      Targets:
        - TargetType: AWSChatbotSlack
          TargetAddress: !GetAtt ChatbotSlackConfiguration.Arn

※参考:

利用時の前提

パイプラインを利用するうえで必要な準備を記載します。

Slackチャンネルへの通知設定

AWS ChatbotとSlackとを連携するため、Chatbotのクライアントに利用するSlackワークスペースを設定します。調べたところ、この設定はマネジメントコンソールからしかできないようなので、AWSにログインして設定します。

また、ChatbotのSlack Channel Configurationを作成するために、SlackチャンネルのIDとワークスペースのIDが必要なので、その情報も取得しておきます。

※参考:

パイプライン利用時のリソース作成

サンプル用のパイプラインを利用するには、以下の様にリソースを作成。配置する必要があります。

  • EKSクラスターの作成
  • sample-resources.yaml のパラメータを修正
    • Parameters の修正
    • CodeBuild EnvironmentVariables の修正
    • EKSクラスターの名称の設定
  • sample-resources.yaml をデプロイ
  • テスト用コードを各CodeCommitリポジトリに格納

EKS aws-authへのIAMロール追加

CodeBuildからEKSへのアクセスを許可するため、CodeBuildに紐づけたIAMロールがEKSにアクセスできるよう aws-auth ConfigMapを編集する必要があります。

$ kubectl edit cm aws-auth -n kube-system

# 以下のような設定を追加

mapRoles: |
   - groups:
     - system:masters
     rolearn: arn:aws:iam::000000000000:role/buildroleforcddeploy
     username: buildroleforcddeploy

※参考:

その他

イメージビルドに失敗する場合

コンテナイメージのビルドに失敗し、以下のようなメッセージが表示される場合があります。

toomanyrequests: You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit

Docker HubのPull回数制限に引っかかった場合にメッセージが表示されるので、気になる場合はDocker Hubにログインする手順を加えます。

※参考:

ArgoCD ver 1.8 ~ 2.2のリリース内容を眺める

つい先日ArgoCD ver 2.2.0がリリースされたので、今回はその内容を眺めてみます。

また、以前ArgoCDを触ってからだいぶ時間が空いてしまい、その間アップデートがいくつもあったので、ver 1.8からおさらいしようと思います。

ver 2.2だけ見たい場合はこちらから飛んでください

なお、過去の性能改善系はなるべく省き、機能追加の部分に絞ってます。

1.8

ver 1.8は2020/12/10にリリースされました。

性能関連

annotation-base path detection

argocd.argoproj.io/manifest-generate-paths というannotationを利用できるようになりました。これを利用すると、ここで指定したパス以外のファイルが変更されても、不要なreconciliationをスキップできます。またこの時に前回のコミットで生成したマニフェストを再利用することで、マニフェストの生成をスキップすることもできます。

Application Controllerの水平スケール

application-controllerは、ターゲットクラスターのキャッシュとアプリのreconciliationを管理する責務を担います。これまでapplication-controllerはArgoCDの中で唯一水平スケールすることのできないコンポーネントで、特にクラスター数が数百を超えるとボトルネックとなるポイントでした。

今回cluster shardを導入することで application controllerを複数稼働できるようになりました。controllerを増やすことでクラスター接続数を増加することができ、reconciliationパフォーマンスも改善することができます。

Core Func

Namespace and CRD creation

namespaceやCRDといったリソースは、他のリソースを作成する前に作成されている必要のあるものです。そのためArgoCDでは PreSync のフェーズよりも前に作成されるのが望ましいです。

これを実現するため、namespaceに紐づいたリソースを検知すると、まずnamespaceを作るように、またCRを作る前にCRDが必要な場合もそれを検知し、作成する様な挙動に変更されました。

unknown field of built-in k8s type

ArgoCD version 1.2から sync-options: validate=false という形で指定すると、デプロイ時のvalidationをスキップすることができます( kubectl apply --validate=false と同様の仕組み)。

これまではvalidationオフの状態でsyncすると、unknown fieldはドロップされ、out-of-syncとは判定されなかったのが、このバージョンからout-of-syncになるよう変更されました。

endpoint diffing

Kubernetes Endpoint Controllerの挙動と、ArgoCDでの差分検出時の挙動がうまくかみ合わず、false-positiveな差分検知をしてしまっていたようです。リリースによりこの問題が修正されました。

app-of-apps health assessment

ArgoCDでは app-of-apps パターンという、Application自体をApplicationで管理するパターンが広く知られています。app-of-apps パターンを採用している場合、子のApplicationがunhealthyになると親のApplicationもunhealthyになっていました。この挙動は多くのユーザーにとって冗長であり、混乱を生む原因であるとして、built-inのヘルスチェックが削除されました。

なおcustom health checkを使用することで、必要に応じて機能を追加することはできます。

Global Project

 ProjectApplication の論理的グループを提供する機能で、複数チームがArgoCDを使うようなマルチテナントな環境で特に有用です。Projectではリポジトリクラスター、リソースの種別を制限するRuleを設定することができます。

Global Projectを利用することで、ほかのProjectに同じ設定を渡すことができます。 argocd-cm ConfigMapに、設定元となるプロジェクトと、それを反映するプロジェクトの条件を追記することで、該当するProjectに指定の設定を反映することができます。

利用できるパラメータは、namespaceやclusterの制限、リポジトリの指定、SyncWindowsなどになります。

コンフィグ管理ツール
  • oci base repo: HelmがOCIのサポートを開始したので、ArgoCDもそれに対応しました。OCI registry内で提供されるHelm chartを利用可能になりました。

  • configurable helm version: ArgoCDはHelm v2/3のどちらのバージョンもサポートします。ver 1.8から、Helmのどちらのバージョンを使用するべきか、chart定義のapiversionで決定されるようになりました。Helmのバージョン違いで互換性の問題が出ると、希望のバージョンで上書きできます。

UI

  • application filter: Application Listページにあるfilterを見つけやすく、条件のクリアもやりやすくなりました。

  • git tag/branch 自動補完: Gitリポジトリのブランチ・タグ名をリストで表示してくれるようになりました。

2.0

ver 2.0は2021/4/7にリリースされました。メジャーアップデートということもあり、新しい機能が多く発表されました。

ApplicationSet

ApplicationSetk8s custom resourceとして利用でき、ArgoCDを拡張することができます。ApplicationSetは先日触れましたが、主な機能としては以下の通りです。

ユースケースとしては、Kubernetesに導入する各種add-onの管理、モノレポでのArgoCDの活用などが挙げられます。特に大規模な環境だと、ArgoCD単一では管理コストが高くなるような設計に対して、 ApplicationSet の利用が役に立つケースは色々とありそうです。

ArgoCD Image Updater

ArgoCD Image UpdaterはArgoCDがApplication で利用するコンテナイメージのレジストリをモニターし、新しいコンテナイメージがアップロードされると、マニフェストを自動で書きかえて更新する機能を提供します。 これと同じ機能はFluxなどでは以前から提供されていた機能ですが、ArgoCDでもこの機能を利用することができるようになりました。

利用時はArgoCDと同じクラスター上にデプロイし、 argocd-cm ConfigMap等に必要な設定を入れれば使えるようです。

ArgoCD Notification

ArgoCDは通知サービスとの連携をするために複数の選択肢が存在しましたが、今後はArgoCD Notificationsでサポートすることになったようです。ArgoCD Notifications自体は以前から存在しましたが、コミュニティなどからのFBを受けて改善をしていたようです。

ArgoCD Notificationsも以前触れたことがありますが、導入はそれほど難しくないので、Slackなどの通知ツールをArgoCDと簡単に連携することができます。

Core feature

Resource deletion/pruning

ArgoCDは Application リソースを削除するとき、もしくはUIから個別リソースの削除を選択すると、Application Sync時に不要なリソースは削除する動きになります。

従来この処理はForegroundで処理をしていましたが、このときArgoCDが不要なリソースを削除しようとしてスタックすることがあったようです。 このバージョンではPrunePropagationPolicy=background という新しいSyncオプションを追加し、backgroundでのリソース削除を選択可能にしました。

Sync changed resources only

これまでArgoCDではSync時はリソース変更の有無にかかわらず、全てのアプリリソースにkubectl applyを実行していました。これは特に数百単位のリソースを扱う際に、Sync完了まで多くの時間を要することがありました。 ApplyOutOfSyncOnly=true というオプションを利用すると、変更リソースのみをsyncすることが可能になりました。

Prune last

PruneLast=true というオプションを追加すると、他のリソースのsyncが完了した後でpruneを実行します。Kubernetesリソース間で依存関係があるような場合に有効になります。

CRDに対するヘルスチェックの追加

ver 2.0ではSealed Secrets / External Secrets / Strimzi CRDに対応しました。

UI

  • Pods view: 全てのリソースを一度に表示するのでなく、Podと関連するリソースのみ表示できるようになりました。Podを管理する上位リソース毎、Node毎など、複数の検索候補を提供します。

  • Logs viewer: 親リソースでPodのログをまとめたり、ログストリームの有効無効化、フィルタリング機能などを実装しました。またargocd CLIではログストリームをサポートしており、 argocd app logs と打つと、Application配下の複数のPodのログを出力することができます。

  • banner feature: バナー上に通知を表示することができるようになりました。

その他

セキュリティ面では、脆弱性への対応、debianからubuntuへコンテナベースイメージの変更など行いました。

2.1

ver 2.1は2021/8/20にリリースされました。

ArgoCD Core

軽量版のArgoCD distributionとしてArgoCD Coreが発表されました。ArgoCDにはマルチテナントをサポートするため多様な機能が含まれますが、それが不要なユーザーはArgoCD Coreを利用し、必要最小限の機能だけを利用することができます。ArgoCD Coreは、ArgoCD自体の機能と、KubernetesビルトインのRBACによるアクセス制御などを提供します。

Core feature

diffing customization

ArgoCDで差分が発生してもそれを無効化するDiffingという機能があります。ver 2.1から、Diffing機能にJQパスを利用できるようになりました。

CRDに対するヘルスチェックの追加

ver 2.1で Trident / Elastic Cloud on Kubernetes / Cluster API / MinIO が追加されました。

リポジトリ登録の簡易化

これまでGitリポジトリをArgoCDに登録するには、argocd-cm ConfigMapに追加する必要がありました (argocd repo add コマンドでも可能)。それがsecretリソースを追加するだけで完了するようになりました。

Enhanced resource customaization

リソースヘルスチェックのカスタマイズが可能になりました。これまでは argocd-cmresource.customization キーを設定することで実現していました。ver 2.1では resource.customizationキーを廃止し、リソースごと(GroupKindごと?)に設定できるようにしました。これにより、kustomizeなどの管理ツールをより使いやすくなったようです。

Kubernetes Secretから秘匿情報を参照可能に

Kubernetes Secretの値をArgoCDの設定に利用することが可能になりました。 argocd-secret という名称のSecretリソースを作成する、または appkubernetes.io/part-of: argocd というラベルを付与したSecretを作成すると、別ファイルから Secretデータ情報を呼び出すことができるようになりました。

ArgoCD server processの設定

オプションとして argocd-cmd-params-cm ConfigMapが導入されました。ArgoCDは複数のサービスが連動して機能を提供しますが、それらサービスに対する設定をこのConfigMapで設定することが可能になりました。

argocd-utilの終了、argocd adminへの移行

ver 2.0で導入された argocd-util CLIが廃止され、同機能が argocd admin コマンドで提供されるようになりました。

argocd admin では、ArgoCDを管理する運用者向けのコマンドが提供されており、管理対象のクラスターのkubeconfigの出力、デプロイ前のArgoCDの設定変更のvalidate、ArgoCDオブジェクトの生成などが可能です。

UI

  • Application Listページの改善: status barの追加、検索ボックスのデザイン変更などがされました。

 

2.2

2021/12/15にリリースされました~。

Project scoped repositories and clusters

これまでのよくある運用フローとして、管理者・運用者がGitリポジトリクラスター登録を行い、開発者は新規で利用するリポジトリなどを申請する形があります。この場合、開発者が新しいリポジトリクラスターを登録したくても権限がなく、運用者の作業を待つ必要がありました。

これに対応するため、Project-scopeなリポジトリクラスターを機能として追加しました。これにより開発者は自分でリポジトリクラスターの追加を実行できるようになります。

この機能は、Project向けにRBACルールを設定して利用可能なリポジトリクラスターを制御し、開発者がRBACに適合したリソースをデプロイすることで実現できます。実際は AppProject リソース中に定義する spec.role 内でポリシーを定義することで、権限を制御するようです。

Config management plugins v2

ArgoCDでは kustomize / helmといったポピュラーなマニフェスト管理ツールと組み合わせることが可能ですが、今回リリースされる Config Management Pluginではさらに拡張可能になります。この機能ではk8sマニフェストを生成するジェネレータとしてスクリプトを利用できるようになります。

2.2ではサイドカーイメージの中でプラグインを起動します。サイドカーイメージはパッケージの依存関係をパッケージすることで、nodejs/pythonといった言語を使うこともできます。

Resource tracking

 argocdは”app.kubernetes.io/instance”というラベルによってリソースを追跡しています。これによる課題として、①ラベルとして使える文字数が63文字しかない、②他のツールがラベルを書き換えると矛盾が発生する、③1クラスターに複数ArgoCDをデプロイしたときにリソースとArgoの関係が分からない、などが出てきました。これを解決するため、新しいannotationとして argocd.argoproj.io/tracking-id を利用できるようになります。

tracking-id に変更すると、Application名だけでなくNamespaceなどの情報も追加されます。このannotationは他のKubermnetesツールとのコンフリクトは発生せず、また一つのクラスター上に複数のArgoCDを導入した場合も区別しやすくなります。

その他

その他の改善として以下のポイントが挙げられています。

  • ArgoCD RBACがregex matchをサポート: これまでの globMatch に加え、regexMatch も利用できるようになりました(こちらの例がわかりやすそうです)。

  • CRDに対するヘルスチェックの追加: あらたに KubeVirt / Cassandra / Openshift Route / Openshift DeploymentConfig / Confluent / Apache Sparkを追加しました。

  • バナー表示の位置を変更可能に: UI上のバナーの位置を画面のtop/bottomに変更可能になりました。

  • AppProjectのdestinationにCluster nameを利用可能に