external-secrets を使い任意のタイミングで Secret を更新する方法

# はじめに
kubernetes には機密情報を管理する手段として Secret リソースが存在しますが、
base64エンコードされたデータを manifest に記載する必要があり、
デコードが容易な点からコードリポジトリで管理することがセキュリティ上好ましくありません。

この問題に対し、GoDaddy社製の[kubernetes-external-secrets](https://github.com/godaddy/kubernetes-external-secrets)というツールを使用しており
ExternalSecret リソースを定義することで、AWS SecretsManager (以下 SecretsManager) で管理する機密情報を Kubernetes の Secret リソースに格納することが可能となります。 [^1]

# external-secrets を使った運用で懸念されたこと
external-secrets はデフォルト設定では 10000ms(10s) 間隔で SecretsManager を polling し、
機密情報に変更があった場合 Secret リソースを 更新します。

Pod のコンテナイメージ と Secrets を同時に更新する必要がある場合、
Deployment のコンテナイメージ更新で実施される rollout restart タイミングと、Secret リソース更新タイミングを合わせることが難しいと懸念されました。

そこで、external-secrets の Secret リソース更新を任意のタイミングで実施できないか調査しました。
その方法について記載します。

# 任意のタイミングで Secrets を更新する方法
## polling を disable にする
external-secrets コントローラーの Deployment リソースの env に`DISABLE_POLLING: true` を追加します。
これによって polling が停止します。

ref. https://github.com/godaddy/kubernetes-external-secrets/tree/master/charts/kubernetes-external-secrets#configuration

※ `true` は任意の値でよい

```yaml:example-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: external-secrets
  labels:
    app.kubernetes.io/name: external-secrets
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: external-secrets
  template:
    metadata:
      labels:
        app.kubernetes.io/name: external-secrets
    spec:
      serviceAccountName: external-secrets
      containers:
        - name: external-secrets
          image: "godaddy/kubernetes-external-secrets:latest"
          imagePullPolicy: IfNotPresent
          resources:
            limits:
              cpu: 100m
              memory: 128Mi
            requests:
              cpu: 100m
              memory: 128Mi
          env:
            - name: "AWS_REGION"
              value: "ap-northeast-1"
            - name: "DISABLE_POLLING"
              value: "true"
```

## ExternalSecret リソースの annotation を更新する
Secret リソースを更新したい場合
ExternalSecret リソースの `spec.template.metadata.annotations` に適当な key-value を追加・上書きします。

annotations が更新されると external-secrets は1度だけ SecretsManager に polling を行い
変更があった場合 Secret リソースを 更新します。

```yaml:example-external-secret.yaml
apiVersion: kubernetes-client.io/v1
kind: ExternalSecret
metadata:
  name: secrets
spec:
  backendType: secretsManager
  data:
  - key: SecretsManager_key
    name: sample_secret
  template:
    metadata:
      annotations:
        timestamp: "202006181000"
```

# 結果
上記の方法で任意のタイミングで Secrets リソース更新することができます。

external-secrets をこの運用に変え
イメージを更新した Deployment と annotation を更新した ExternalSecret の manifest を同時に apply することで
Deployment のコンテナイメージ更新で実施される rollout restart タイミングと、Secret リソース更新タイミングを合わせることができます。

ただし、external-secrets の Secret リソース更新が何かしらの理由で遅延するなどした場合
更新前の Secret で Pod が起動してしまう可能性はありえると考えております。

このあたりは他により良い方法がないか、引き続き考えていこうと思っています。
（external-secrets に限らず、この問題に対し良い方法をご存知の方がおりましたらコメントいただけますと幸いです。）

# まとめ
external-secrets を使い任意のタイミングで Secret リソースを更新する方法を共有しました。
この方法を使うことで、Pod のコンテナイメージ と Secrets を **ほぼ** 同時に更新することができます。

external-secrets は簡単に機密情報管理サービスから kubernetes に機密情報を注入できます。
GCP や Alibaba Cloud をサポートするなど活発に更新が行われていますので
ご興味ありましたら一度お試しください。

[^1]: AWS Secrets Manager 以外にも複数の機密情報管理サービスと連携できます [ref.](https://github.com/godaddy/kubernetes-external-secrets#backends)