ArgoCD+ECR+GitLabを使った自動デプロイを設定する

# 環境説明

## AWS

すべてAWS上に構築します。プライベートGitLabはGitLab公式が出しているAMIを使ってEC2でデプロイします。K8sはEKSです。ワーカーはEKS Managed Workerを使用します。GitLabとWorkerは同じVPC内に配置しています。また、コンテナレジストリはECRを使用します。

![aws.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1356950/279f798b-6403-356f-b2f1-cc7ad85c85af.png)


## K8s

最終的に構成する環境イメージです。ns:argocd内にargocd関連のリソースをデプロイします。argocdのimage-updaterを使用してECRのイメージ更新を検知してマニフェストを自動で書き換えます。ECRに接続するため接続用Secretを作成します。Secretは12時間で有効期限が切れるためCronJobで定期的に更新します。argocdによりk8sレポジトリの内容がns:defaultにデプロイされます。

![k8s.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1356950/e1556175-87d7-e2ab-a0cd-205430378f05.png)


## デプロイフロー

処理の順番は左から右に進んでいきます。アプリケーションのレポジトリを更新するとGitLabCICDにより自動でコンテナビルドしECRへプッシュされます。ECRのイメージが更新されるとargocd-image-updaterにより検知されk8sレポジトリの内容を書き換えます。k8sレポジトリの内容が変更されるとwebhookによりargocd-serverへ通知され、その後argocd-serverによりk8sレポジトリの内容がデプロイされます。

![cicd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1356950/9f5535a1-880a-1fe8-83ae-fdc5b0ab46a2.png)


## 各種バージョン

- EKS： 1.20
- GitLab：　CE 14.0.5
- ArgoCD：　2.0.4
- ArgoCD Image Updater：　0.9.5

---

# 前提

AWS環境(VPC、EKS、IAM、ECR等)は構築済みを想定しています。

# GitLabCICDの設定

コンテナをビルドしてECRにプッシュする処理を自動化します。

## rootのパスワード変更

GitLab公式のAMIでデプロイしたEC2にログインします。GitLab CE 14.0.5のAMIだと初回ログイン時にrootのパスワード設定ができなかったためEC2にsshでつないでrootのパスワードを変更します。やり方はこちらの[GitLabのrootパスワード変更方法](https://qiita.com/crnls1985/items/4fb42d803b0685eff092)を参考にしました。

- EC2にsshでログインします。ログインユーザはubuntuです。

- railsコンソールを起動します。

``` sh
$ sudo gitlab-rails console -e production
(しばらく時間かかります。)
```

- rootユーザになります。

``` sh
> user = User.find(1)
```

- パスワードを設定します。以下例だと`password`で設定しています。

``` sh
> user.password = 'password'
> user.password_confirmation='password'
```

- 設定を保存します。保存したらexit等で抜けます。これでrootログインできる様になります。

``` sh
> user.save!
```

## ユーザ追加

- GitLabのログインページにある`Register now`から必要情報を入力してユーザを作成します。ユーザを作成しただけではログインできません。今回はmo-riというユーザを作成します。

- rootでログインし、menu→adminを表示します。

- Overview→Usersを開き、Pending approvalタブを開くと先ほど作成したユーザが表示されるので歯車マークからapproveします。approveしたらrootからsign outします。

- 先ほど作成したユーザでログインします。

## Runnerの追加

GitLab CICDのジョブを実行するRunnerを追加します。Runnerはk8sにデプロイします。また、ExecuterはK8s Executerを使用します。手順はこちらのGitLab公式ドキュメント[Installing GitLab Runner using the Helm Chart](https://docs.gitlab.com/runner/install/kubernetes.html#installing-gitlab-runner-using-the-helm-chart)を参考にして進めます。

- まずは先ほどログインしたユーザで適当にプロジェクトを作成します。今回は`test-app`という名前のプロジェクトを作成します。（プロジェクトでなくグループのruunerとして追加する場合はグループを作成してください。）

- プロジェクトを作成したら左メニューのSettings→CICDを開き、Runnerをexpandします。`Set up a specific runner manually`にURLとTokenが表示されているのでコピーしておきます。（あとでRunnerを設定するときに使います。）

- k8sにRunnerをデプロイします。デプロイはHelmを使います。まずはレポジトリを追加します。

``` sh
$ helm repo add gitlab https://charts.gitlab.io
```

- valueファイルを入手します。

``` sh
$ wget https://gitlab.com/gitlab-org/charts/gitlab-runner/-/raw/main/values.yaml
```

- valueファイル内の値を修正します。とくに`gitlabUrl`と`runnerRegistrationToken`をコメント解除し先ほど確認したURLとTokenを設定します。また、ServiceAccount等も自動で作って欲しいので`rbac.create`もtrueに設定しておきます。さらに、`tags`もわかりやすく設定しておきます。今回はk8sにしておきました。

- Runnerをデプロイするns:gitlabをつくります。

``` sh
$ kubectl create ns gitlab
```

- Runnerをデプロイします。

``` sh
helm install --namespace gitlab gitlab-runner -f values.yaml gitlab/gitlab-runner
```

- デプロイ確認

``` sh
kubectl get pod -n gitlab
```

- 先ほどURLやTokenを確認した画面にRunnerが追加されていればOKです。もしうまく追加されない場合、GitLabのSGでK8sワーカーからのインバウンドが許可されているか確認してください。

## ExecutorsからECRへのアクセス設定

ExecutorsからECRへイメージをプッシュするため、ExecutorsにECRへのアクセス権を与えます。iam-for-saでExecutorのsa（ns:gitlabのdefault）にAmazonEC2ContainerRegistryPowerUser等のポリシーを与えます。権限を付与したIAMロールを作成しておきsaにannotationsを追加して設定します。iam-for-saを使用するためのOIDCの設定は[こちら](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html)、IAMロールの作成方法は[こちら](https://docs.aws.amazon.com/eks/latest/userguide/create-service-account-iam-policy-and-role.html)、saに設定するannotationについては[こちら](https://docs.aws.amazon.com/eks/latest/userguide/specify-service-account-role.html)を参照。

## プロジェクトの準備 & 動作確認

- `test-app`プロジェクトに適当なDockerfileを配置します。また、`.gitlab-ci.yml`をレポジトリのrootに配置します。例として以下のような`.gitlab-ci.yml`を使います。ECR_HOSTは自身のECRに置き換えてください。以下はプロジェクト名:コミットハッシュという名前のイメージをビルドし、ECRにプッシュします。**なお、ECRにはあらかじめプッシュ先のレポジトリ（下記例だとプロジェクト名）を作成しておく必要があります。**

``` yml
stages:
  - container-build

# docker build & push の工程です。
docker-build:
  image: 
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  stage: container-build
  variables:
    ECR_HOST: <accountid>.dkr.ecr.<region>.amazonaws.com
  script:
    - |
          cat > /kaniko/.docker/config.json <<EOF
          {
            "credHelpers": {
              "${ECR_HOST}": "ecr-login"
            }
          }
          EOF
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination ${ECR_HOST}/$CI_PROJECT_NAME:$CI_COMMIT_SHORT_SHA
  only:
    - main
  tags:
    - k8s
```

- ファイル群を配置したらコミットしてmainにpushしてください。プロジェクトの左メニュー、CICD→Pipelinesから状況を確認できます。passedになれば成功です。

- ECRにビルドしたイメージが格納されていることを確認します。

# ArgoCDの設定

GitレポジトリにあるマニフェストをK8sへ自動デプロイします。

## ArgoCDのデプロイ


公式ドキュメントの[Getting Started](https://argo-cd.readthedocs.io/en/stable/getting_started/)を参考に進めます。

- ns:argocdを作成しargocdをデプロイします。

``` sh
$ kubectl create namespace argocd
$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
```

- argocdのCLIツールを作業端末にインストールします。

``` sh
$ brew install argocd
```

- argocdにアクセスするためsvc:argocd-serverのtypeをLoadBalancerに変更します。（本当はALB ControllerとIngressで公開したかったけど時間かかりそうだったのでいったんこのやりかた。やり方わかったら更新する。）

``` sh
$ kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'
```

- これだとargocdに誰でもつなげてしまう。argocdへのアクセス許可を設定したSecurityGroupを用意しておき、svc:argocd-serverにannotationでSecurityGroupを指定。なお、ここで使用するSGには作業端末とGitLabのパブリックIPからの80および443を許可しています。

``` sh
$ kubectl edit svc argocd-server -n argocd
```

変更内容

``` yaml
apiVersion: v1
kind: Service
metadata:
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-security-groups: <sgid>
...
```

- argocdに接続するためロードバランサーのパブリックDNS名を確認しておきます。

``` sh
$ kubectl get svc -n argocd | grep argocd-server
```

- adminのデフォルトのパスワードはsecret:argocd-initial-admin-secretにあります。以下コマンドでパスワードを表示します。

``` sh
$ kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
```

- CLIツールでargocdにadminでログインします。

``` sh
$ argocd login a64083db52f2c443f91ca170959e5v7d-1401768371.ap-northeast-1.elb.amazonaws.com
> Username: admin
> Password: <pass>
```

- ログインしたらadminのパスワードを変更しておきます。

``` sh
$ argocd account update-password
*** Enter current password: <pass>
*** Enter new password: <new pass>
*** Confirm new password: <new pass>
```

- secret:argocd-initial-admin-secretはもう不要なので削除しておきます。

``` sh
$ kubectl delete secret -n argocd argocd-initial-admin-secret 
```

## マニフェストの準備

GitLabに適当なプロジェクトを作成します。今回はk8sという名前にします。プロジェクト内にはkustomize形式のマニフェストを配置します。後述するimage updaterによるイメージタグの自動更新をするにはhelmまたはkustomizeの形式である必要があるためです。[ネタ元](https://argocd-image-updater.readthedocs.io/en/stable/configuration/applications/)

今回は以下のようなシンプルな構成にしました。deploymentで使用するコンテナイメージはGitLab CICDでビルドしたコンテナイメージを指定しています。ServiceはType:LBです。overlay/dev/kustomization.yamlはbaseを読み込むだけのシンプルな設定をしています。

```
.
├── overlay
│   └── dev
│       └── kustomization.yaml
└── base
    ├── service.yaml
    ├── kustomization.yaml
    └── deployment.yaml
```

## アプリケーションの設定

argocdの中ではアプリケーションという塊でデプロイを設定します。今回はk8sレポジトリが更新されたら自動でsyncするように設定します。

- argocdのtype:lbのDNS名を指定し、webブラウザでアクセスします。adminでログインします。

- 左メニューの歯車アイコンを選択し、manage your repositories→repositoriesを開きます。

- 画面上部のconnect repo using httpsを選択して以下情報を入力してconnectします。この時、GitLabのSGにk8sワーカーからのインバウンドが許可されていないとうまく通信できない恐れがあります。

``` yaml
type: git
Repository URL: http://ec2-18-221-49-253.us-east-2.compute.amazonaws.com/mo-ri/k8s (対象レポジトリのクローンURLから末尾の.git消したやつ)
Username: GitLabのユーザ
Password: GitLabユーザのパスワード
```

- 左メニューの四角3つ重なったアイコンを選択し、NEW APPを選択して以下情報を入力してcreateする。

``` yaml
GENERAL:
  Application Name: test-app (好きな名前)
  Project: defautl
  Sync Policy: Automatic
  Sync Option: 変更なし(すべてデフォルト)
SOURCE:
  Repository URL: http://ec2-18-221-49-253.us-east-2.compute.amazonaws.com/mo-ri/k8s　(対象レポジトリのクローンURLから末尾の.git消したやつ)
  Revison: main
  Path: overlay/dev　（リポジトリ内のapply対象kustomaization.yamlがあるディレクトリのパス）
DESTINATION:
  Cluster URL: https://kubernetes.default.svc （argoデプロイしたクラスタが対象なら左記ClusterIPのServiceを指定）
  Namespace: default
Kustomize: 変更なし(すべてデフォルト)
```


- うまくデプロイできれば画面にアプリケーションが表示される。さらにアプリケーションを選択する含まれるリソースが表示される。黄色はsync中で全体的に緑になればsyncできています。

- argocdによりデプロイされたns:defaultにあるserviceのEXTERNAL-IPをブラウザ等で指定してアクセスできることを確認してください。

# Image Updaterの設定

コンテナレジストリに最新のコンテナイメージが格納されたら自動でマニフェストを更新してデプロイするようにします。

## ECRのシークレット

image updaterからECRに接続するためECR接続用のシークレットを作成します。12時間でシークレットは切れてしまうため自動更新する仕組みを使います。以前別で書いた[K8sで使うECRアクセス用のSecretを自動更新する](https://qiita.com/moriryota62/items/7d94027881d6fe9a478d)のやり方を使います。事前にECRへ接続可能なIAMユーザを作成しておきそのユーザーのアクセスキーとシークレットキーをbase64エンコードした値で指定します。シークレットの配置先はns:argocdを指定します。

上記の手順だとデフォルトでは8時間毎にシークレットを更新します。しかし、夜間や休日はノードを停止したいため、Jobを実行できない時間帯が存在します。
平日朝9時にノードが起動するようにオートスケールをスケジュールするので起動してからPodが実行できるまでの余裕を加味し、平日朝9時5分にシークレットを更新するように設定します。具体的にはCronJobのスケジュールを以下に設定します。K8sはUTCのため日本時間から-9します。

``` 
schedule: '5 0 * * 1-5'
```

## Image Updaterのデプロイ

公式の[Getting Started](https://argocd-image-updater.readthedocs.io/en/stable/install/start/)を参考に進めます。

- image updaterをデプロイします。

``` sh
$ kubectl apply -n argocd -f "https://raw.githubusercontent.com/argoproj-labs/argocd-image-updater/v0.9.5/manifests/install.yaml"
```

- デフォルトのimage updaterは2分間隔でコンテナレジストリを確認します。もっと早くしたい場合、deployment:argocd-image-updaterのcommnadに`--interval`を追加します。[ネタ元](https://argocd-image-updater.readthedocs.io/en/stable/install/running/#flags)

``` yaml
      - command:
        - /usr/local/bin/argocd-image-updater
        - run
        - --interval=30s # これだと30秒間隔になる。なお、1分未満にすると起動時に警告がでる。
```

- ECRのレジストリ設定を追加します。

``` sh
$ kubectl edit configmap -n argocd argocd-image-updater-config
```

設定例。dataとして以下を追加します。アカウントIDとリージョンは変えてください。pullsecretで先ほど作成したECRアクセス用のsecretを指定します。

```
data:
  log.level: debug
  registries.conf: |
    registries:
    - name: My ECR
      api_url: https://<accountid>.dkr.ecr.<region>.amazonaws.com
      prefix: <accountid>.dkr.ecr.<region>.amazonaws.com
      ping: no
      credentials: pullsecret:argocd/aws-registry
```

- configmapを設定したら念の為image-updaterのpodを削除してセルフヒーリングで再作成します。

- イメージ自動更新対象のアプリケーションにannotationsを追加します。web画面からだとaplicationを選択した画面の左上にAPP DETAILSがあります。summaryタグのeditで以下のannotaionsを追加してsaveします。

```
argocd-image-updater.argoproj.io/image-list = my-image=<accountid>.dkr.ecr.<region>.amazonaws.com/test-app
argocd-image-updater.argoproj.io/my-image.update-strategy = latest
argocd-image-updater.argoproj.io/write-back-method = git
```

- 動作確認のためGitLabのtest-appレポジトリを更新します。コンテナがビルド&プシュされた後、自動でk8sレポジトリも更新されるはずです。また、Podも最新イメージのものに置き換わるはずです。ただ、デフォルトではargocdの同期間隔が3分で設定されています。そのため、test-appレポジトリを更新してから最新イメージに置き換わるまで少し（長くて5分くらい）時間がかかります。

## webhook

デフォルトだとマニフェストの変更検知は3分間隔で行われます。それが遅いと感じる場合、Gitレポジトリ側でWebhookを設定することで早くできます。[ネタ元](https://argoproj.github.io/argo-cd/operator-manual/webhook/)

### GitLab側の設定

- k8sマニフェストを管理しているレポジトリでsettings→Webhooksを開き以下情報を入力してwebhookを追加します。このとき、Argo用のServiceType:LBでデプロイしたCLBについてるSecurityGroupでGitLabからの80と443を許可しておきます。

``` yaml
URL: http://a64083db52f2c443f91ca170959e5v7d-1401768371.ap-northeast-1.elb.amazonaws.com/api/webhook　(argo用LB)
secret token: <適当なパスワード>
Enable SSL verification: チェックを外す（今回TLSしてないため）
```

すべて設定したら画面下部にtestボタンがあるためテストして見ると良いでしょう。

### Argo側の設定

- ns:argocdにsecret:argocd-secretがあるのでその中に先ほどGitLabで設定した`secret token`を設定します。`secret token`に指定したパスワードをbase64エンコードします。

``` sh
echo -n <適当なパスワード> | base64
```

- 以下コマンドでsecret:argocd-secretを修正する。

``` sh
kubectl edit secret argocd-secret -n argocd
```

修正内容

``` yaml
data:
  webhook.gitlab.secret: <base64>
```

- 上記修正したらargo-serverのPodを消してセルフ・ヒーリングさせます。

これをやればk8sレポジトリが更新されるとすぐにクラスタへデプロイしてくれる。image updaterのintervalとwebhookを設定すると更新まで5分ほどかかっていたのが1分未満まで短くなりました。（コンテナの大きさによる。）