負債化してしまったAWSのあれこれをコンテナ基盤に乗せて資産化する

[AWS Containers Advent Calendar 2023](https://qiita.com/advent-calendar/2023/aws-containers) 1日目の記事です。

定期的にちょこっとスクリプトを叩くなど、補助的な役割をするEC2やLambda、
みなさんの環境にもたくさんあると思います。
もちろんこれらは素晴らしいサービスであり、
我々の日々の運用を支えてくれるものです。
ただ、これらをきちんと保守するフローや仕組みがあれば良いのですが
弊社の場合はそれがなく、負債となっていました。
このため負債を解消すべく、コンテナ化しつつアプリケーション側の実行基盤に載せ替えることで
運用保守をしやすくする改善活動を続けています。

## こんなこいるかな

みなさんのAWSアカウントの中に、こんな子はいないでしょうか。

- cron起動で、RDSにSQLを流してメトリクスを取っているレジェンドEC2
- 定期的にOpenSearchのmergeをしているレジェンドLambda

サービスを提供するメインのインフラ基盤の傍らで、細かい管理系の処理を行ってくれる子たちです。
最近であればLambdaで実装することのほうが多いのではないでしょうか。
Lambdaを使えばちょっと書いてしまえばサーバーレスで動いてくれるのがいいところです。

## 管理系処理実行EC2 or Lambdaの課題

弊社でもそのようなEC2やLambdaが多数存在していました。
ただこういうものに限って、いざ更新するとなったときに
ソースコードはあるもののデプロイは手動でやっていた、とか
slsを使ってデプロイしていたけどローカルPCから、とか
デプロイするまでに一手間必要だったりしました。
特にLambdaは定期的にランタイムの更新が発生するので
コードに変更が必要な場合はやむなく手動で関数を更新したりすることもありました。
またこれが頻繁に発生するものでもないので整備が進まない、
というよくある悪循環になっていました。

## コンテナ化すると解決する？

そこでコンテナ化してしまいましょう！と言いたいところですが
コンテナ化するだけでは問題は解決しません。
Lambdaなどランタイムに依存するところの開発・修正・動作確認といった観点では
可搬性の優れたコンテナにすることで一定楽になります。
コンテナ化が大事、というよりも、コンテナ化することのメリットもあるし
何よりサービスを提供しているアプリケーションと同じ基盤で動かし、
デプロイ方法を統一できれば今後変更があった場合でも保守がしやすくなります。
なのでコンテナ化するといっても、闇雲にただコンテナ化するのではなく、
メインのアプリケーションがコンテナで動いており基盤が整っていることが前提になります。

## 実例：Lambda pythonスクリプトをEKS Cronjob（shell）に置き換える

実際に弊社で行った移行の話をします。
弊社に先程紹介したこんな子、いました。

- 定期的にOpenSearchのmergeをしているレジェンドLambda

Python3.6で動いていて、ランタイムのサポートアウトにより更新する必要がありました。
3.7にバージョンアップしてもよかったのですが、CDの部分が整備されておらず
動作確認もし辛い状態であったため、
メインのアプリケーションが動いているEKSへと移行することにしました。
処理自体も簡単なものでありPythonである必要がなかったので、shellで書き直して
KubernetesのCronjobとして定期的に起動するようにしました。
弊社ではhelmfileとArgo CDを活用してアプリケーションのGitOpsを推進しています。
今回のOpenSearch管理スクリプトも同様にGitOpsに乗せることで
動作確認用スクリプトのデプロイが簡単になり、それにより動作確認も簡単になり
また変更が発生した場合でもどこにスクリプトがあるのか、どこを変更すればよいのか、
どうデプロイすればよいのかがわかりやすくなりました。
helmfileを使った具体的な構成は以下のようになります。

```
.
├── environments
│   └── values.yaml
├── helmfile.yaml
└── manifests
    ├── merge.sh
    └── manifest.yaml.gotmpl
```

manifests配下にgotmpl形式で拡張可能にしたKubernetesのマニフェストと実際の処理を行うshellを格納しています。次はhelmfileです。

```helmfile.yaml
environments:
  {{ .Environment.Name }}:
    values:
    - environments/values.yaml

---

releases:
  - name: "merge"
    namespace: "{{ .Namespace | default .Values.merge.namespace }}"
    chart: "./manifests"
    installedTemplate: "{{ .Values.merge.installed }}"
    labels:
      namespace: "{{ .Values.merge.namespace }}"
      default: "false"
```

manifests配下のマニフェストファイルをChartとみなして、以下のgotmplをベースとしてマニフェストを生成し、ConfigMapでshellをマウントしつつCronJobで定期的に実行するようにしています。

```manifest.yaml.gotmpl
apiVersion: v1
kind: ConfigMap
metadata:
  name: merge
data:
  merge.sh: |
    {{- readFile "merge.sh" | nindent 6 }}
---
apiVersion: batch/v1
kind: CronJob
metadata:
  name: merge
spec:
  schedule: "5 01 * * *"
  timeZone: "Asia/Tokyo"
  concurrencyPolicy: "Forbid"
  successfulJobsHistoryLimit: 10
  failedJobsHistoryLimit: 5
  jobTemplate:
    spec:
      backoffLimit: 0
      template:
        spec:
          restartPolicy: Never
          serviceAccountName: {{ .Values.merge.serviceAccountName }}
          containers:
          - name: merge
            image: chatwork/aws:2.8.10
            env:
              - name: ES_DOMAIN_ENDPOINT
                value: {{ .Values.merge.esDomainEndpoint }}
            command:
              - "/bin/bash"
              - "/tmp/merge.sh"
            volumeMounts:
            - name: merge
              mountPath: /tmp
          volumes:
          - name: merge
            configMap:
              name: merge
              items:
                - key: merge.sh
                  path: merge.sh
```

## デプロイフローを整えてよかったこと

先程述べた通り、
> 何よりサービスを提供しているアプリケーションと同じ基盤で動かし、
デプロイ方法を統一できれば今後変更があった場合でも保守がしやすくなります。

この恩恵をうけることができました。
特にhelmfile manifestsの仕組みが強力で、
簡単なスクリプトであればここに集約できかつGitOpsできてしまうため
もう全部これでいいんじゃないかなと思っています（暴論）。
また、今までこのような管理系の処理コードはインフラ・SREチームでしか見れないリポジトリにあったことで完全に閉じてしまっていましたが、
デプロイフローをアプリケーション側に寄せたことで、開発者の人も見れるようになり、みんなで保守できるようになりました。

## まとめ

弊社はEKSを使っているためKubernetesで稼働するような仕組みを整えましたが
ECSをお使いの場合でも似たような構成を取ることは可能かと思います。
EKSという観点ではArgo CDによるGitOpsという非常に強力なデプロイ方法があるので、
この仕組みに乗っからない手はない、とつくづく思っています。
いずれにしても大切なのは、コンテナ化をする利点を活かしつつ、普段使っている基盤・デプロイの仕組みを活用することです。
みなさんのAWSアカウントでひっそり動いている負債化してしまったリソースたち、
それらの解消のヒントにつながれば幸いです。