k8s で ECR の認証情報 Secret を定期的に取得する

 ECR から Docker のイメージをプルするための認証情報は `aws ecr get-login-password` で作成できます。k8s ではこの認証情報を Secret として保存することで、イメージをECR からプルしてくれます。しかし、この方法で取得した認証情報は12時間しか有効ではないため、定期的に認証を取得する必要があります。
 k8s が動いているホストの cron で実行してもいいのですが、色々な事情によって k8s の cronjob を使ってやることにしました。
今回はその方法を記事にしようと思います。
# 前提
- k8s がすでに用意されている
- docker イメージをパブリックに公開できる環境がある。または、パブリックにある docker イメージを自己判断で使用できる環境である。
- ECR にプライベートなイメージがすでに用意されている

# 流れ
1. aws のログイン情報を Secret として登録
2. aws-cli と kubectl を使用できる Docker イメージを作成する
3. ServiceAccount と ClusterRoleBinding を作成する
4. cronjob で定期的に ECR への認証情報を取得して Secret として保存

## 1. aws のログイン情報を Secret として登録
Secret を作成するために、AWS のログイン情報をファイルにまとめて起きます。
今回は `.aws.credentials` という名前で作成します。
```.aws.credentials
AWS_ACCOUNT_ID=123456789012
AWS_ACCESS_KEY_ID=XXXXXXXXX
AWS_SECRET_ACCESS_KEY=YYYYYYYYY
AWS_DEFAULT_REGION=ap-northeaset-1
```
このファイルから Secret を作成しておきます。これはこの後の3の手順で Pod 内から使用します。
今回はSecret名を `aws-credentials` にしています。 Secret は名前空間を超えることはできないので注意してください。
```shell
kubectl create generic aws-credentials --from-env-file=.aws.credentials
```

## 2. aws-cli と kubectl を使用できる Docker イメージを作成する
k8s の cronjob で aws-cli と kubectl を実行するための Docker イメージを作成します。
すでに公開されている Docker イメージを使用するのであれば、このステップは飛ばすことができます。自己判断で決めてください。

### イメージを自作する場合
alpine ベースの Docker イメージを作成します。
```Dockerfile:Dockerfile
FROM alpine:3.12
RUN apk --no-cache add aws-cli wget \
    && wget https://storage.googleapis.com/kubernetes-release/release/v1.24.0/bin/linux/amd64/kubectl \
    && mv kubectl /usr/local/bin/kubectl \
    && chmod +x /usr/local/bin/kubectl \
    && apk del wget
```
`v1.24.0` 部分が kubectl のバージョンです。
バージョン部分はハードコーディングしていますが、[公式](https://kubernetes.io/ja/docs/tasks/tools/install-kubectl/)には最新版をインストールする方法の記載があります。
次のステップで説明しますが、Pod 内では kubectl で使用する config ファイルが Pod 起動時(?)に用意されます。そのため、このイメージに kubectl のための config ファイルを入れる必要はありません。パブリックとして公開するため、間違えてもいれないようにしてください。
Docekrfile を作成したら、パブリックなイメージとして docker hub や ECR などにプッシュします。
```shell
docker login
docker push my-image:latest
```
## 3. ServiceAccount と ClusterRoleBinding　を作成する　

続いて、Pod から Secret を作成できるように権限を設定していきます。
k8s には Role Based Access Control(RBAC) という機能があります。リソースへのアクセス権限を設定した Role を付与することで、 リソースへのアクセスを許可することができます。
今回はクラスターで共通の ClusterRole を使用していきます。（クラスター全体ではなく、Namespace ごとに分離して権限を作成したい場合は Role を使用できます）

### ServiceAccount の作成
まずは ServiceAccount を作成します。
```shell
# kubectl -n default create sa <ServiceAccount 名>
kubectl -n default create sa secrets-manager
```
ServiceAccount は 1:N の関係で ClusterRole と紐づけることができます。
ServiceAccount は Pod のマニュアルで指定します。

### ClusterRole の作成
つづいて、ClusterRole を作成します。
```yaml:secrets-cr.yml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: secrets-cr  # ClusterRole の名前
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["create", "updated", "delete"]
```

`rules.resource` に `secrets` と記載しています。これで secret-cr が紐づいたリソースは secret に対して作成・更新・削除ができるようになります。 `verbs` には他にも `get` `list` `watch` などがあります。

### ClusterRoleBinding で紐づける
最後に、作成した SecretAccount と　CluterRole を紐づけます。紐付けには ClusterRoleBinding を使用します。（もし Role を作成していた場合は、RoleBinding）

```yaml:secrets-crb.yml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: secrets-crb  # ClusterRoleBinding の名前
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: secrets-cr  # 紐づける ClusterRole
subjects:
- kind: ServiceAccount
  name: secrets-manager  # 紐づける ServiceAccount
  namespace: default
```
ClusterRoleBinding は最後に作成しないとエラーになるのでご注意ください。

## 4. Secret を更新する Cronjob を作成

最後に cronjob　を作成して完成です。

```yaml:aws-secret.yml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: create-aws-secret
  namespace: default
spec:
  schedule: "* */10 * * *"  # 有効期間は12時間
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          serviceAccount: secrets-manager  # 3. で作成した ServiceAccount を設定
          containers:
            - name: create-aws-secret
              image: < 2. で作成したパブリックな Docker　イメージ>
              imagePullPolicy: IfNotPresent
              env:
                - name: SECRET_NAME
                  value: aws  # 作成・更新する secret の名前
              envFrom:
                - secretRef:
                    name: aws-credentials  # 1. で作成した aws 認証情報
              command:
                - /bin/sh
                - -c
                - |-
                  kubectl delete secret --ignore-not-found ${SECRET_NAME}
                  kubectl create secret docker-registry ${SECRET_NAME} \
                  --docker-server=${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com \
                  --docker-username=AWS --docker-password=$(aws ecr get-login-password)

```