良いエンジニアライフを送ろう!
Libryでスクラムマスター兼フロントエンドエンジニアをしている岩西である。
今回はフロントエンドエンジニアと称しながら、その枠を超えてLibryのインフラ部分の運用改善にどのような取り組みをしているのか、一部分だけでも技術的に掘り下げて話していけたらと思う。
ここでは上の構成図の青枠で囲んだ部分(CD)を中心に書いていこうと思う。
Libryの運用改善に対する取り組み
Libryではプロダクトの改善に加えて、サービス運用改善に向けても取り組んでいる。
例えば自社開発のプロダクトをDockerコンテナを使って開発〜本番環境まで一貫して実行できるように、コンテナ周りの環境整備を徐々に進めていったりとかをしている。
そしてスモールスタートで本番環境にKubernetesを使ったコンテナオーケストレーションツールの導入も進めている。
実際に現在Libry周辺プロダクトの一部サービスはKubernetes上で稼働している。
無論まだまだこのあたりのツール運用には課題があり、そこを少しずつ取り除いていきながら従来よりもインフラ周りで運用しやすい環境を作っていく必要があるが、近いうちEC2上で稼働する全てのプロダクトをコンテナで置き換えKubernetes上に載せることになるだろう。
EKS on Fargate
Dockerコンテナを実行する環境の候補としてAWSの場合ECSやEKS、最近だとRed Hat OpenShiftを使う方法も考えられる。
GCPの場合だとGCEやGKE、Cloud Runあたりが検討に上がってくるだろう。
Libryではクラウド環境としてAWSを採用していたこともあり、スモールスタートで取り組むためAWS上でDocker実行環境を構築することになった。
その中でちょうど今回の取り組みを始めたタイミングでFargateがEKSサポートしたこともありEKS on Fargateが候補となった。
EKSはAWSが提供しているフルマネージド型のKubernetesサービスであり、ざっくり言ってしまうとマスターノードの部分をよしなにしてくれるサービスとなっている。
そのためFargateが来る前はEC2のワーカーノードを立てて管理する必要があった。
しかしFargateによるサポートが入ったEKSでは、このワーカーノードの部分もよしなにしてくれるようになり、より管理の手間を省くことができるようになってきた。(もちろん、その分EKS on Fargate固有の制約もあったりする。)
今回EKS on Fargateを使うことでKubernetesの知見を活かしつつ、サーバー管理の手間を省いたり、スケーリングによる効率的なコンピューティングリソースの活用を実現できたりするのではないかと考え、EKS on Fargateを使ってコンテナ運用環境を構築することを決定した。
eksctlを使ってEKSクラスターを作成
ここではサンプルとしてtest-clusterというEKSクラスターを作成する方法を書き残す。
EKS周りのsetupにはeksctlが使いやすい。
Fargate profileやKMSのサービスアカウントなどを定義した、ざっくりと以下のようなYAML(VPCの設定やPolicyは適宜書き換える必要あり)を書いて保存し、
./path/to/test-cluster.yaml
apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: test-cluster region: ap-northeast-1 version: "1.16" vpc: subnets: public: ap-northeast-1a: id: subnet-XXXXXXXXXXXXXXXXX ap-northeast-1c: id: subnet-XXXXXXXXXXXXXXXXX ap-northeast-1d: id: subnet-XXXXXXXXXXXXXXXXX private: ap-northeast-1a: id: subnet-XXXXXXXXXXXXXXXXX ap-northeast-1c: id: subnet-XXXXXXXXXXXXXXXXX ap-northeast-1d: id: subnet-XXXXXXXXXXXXXXXXX fargateProfiles: - name: fp-default selectors: - namespace: default - namespace: kube-system - name: fp-flux selectors: - namespace: flux iam: withOIDC: true serviceAccounts: - metadata: name: key-management-service namespace: default attachPolicyARNs: - "arn:aws:iam::XXXXXXXXXXXX:policy/KeyManagementServiceReadIAMPolicy" cloudWatch: clusterLogging: enableTypes: - "*"
eksctlのインストールやAWSとの認証などを済ませた環境で以下のコマンドを叩くとEKS on Fargateのクラスターが作成できる。
eksctl create cluster -f ./path/to/test-cluster.yaml
AWSコンソールや kubectl
でtest-clusterが作成されたことが確認できる。
GitOpsとFlux
LibryではGitHubを使ってソースコードを管理しており、KubernetesのmanifestsなどもGitHubを使って管理している。
インフラ環境を開発者が容易に参照でき、コード上での変更・レビューができるようにGitHubをSingle Source of Truth(信頼できる唯一の情報源)としたGitOpsをLibryの運用方針として取ることにした。
GitOps実現においては、CNCFのsandbox projectにもなっているFluxを使ってみることにした。
FluxではKubernetes側からpullしてmanifestsを適用していくので、CIツールにkubectl操作のための設定する必要がなく、セキュアで手軽である。
ちなみにもう一つ有名なGitOpsツールとしてArgoがあり、さらにその2つが力を合わしたArgo Flux projectが動いていたりしている。
この中で言及されているGitOps Engineは、今の計画ではFlux v2と統合されないという旨のアナウンスがでていて今後の動向を注意深く見ていく必要があるが、近い将来より充実したGitOps環境が実現できるようになっているかもしれない。
Fluxのsetup
Fluxの使い方は簡単で、ここのGet Startedに沿って進めていけばいい。
fluxctlをインストールし、Kubernetes上にflux用のnamespaceを用意する。
kubectl create namespace flux
Kubernetesのmanifestsなどを配置するgit repository(LibryではInfra repositoryと呼んでいる)を作成し、そこの任意のディレクトリにmanifestsを配置する。
manifestsは何でも良いが、今回はサンプルとして以下を使用する。
./path/to/deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: sample-deployment spec: selector: matchLabels: app: sample replicas: 3 template: metadata: labels: app: sample spec: containers: - image: busybox:1.32.0 name: sample command: - "/bin/sh" - "-c" - | echo "started" while : do date sleep 1m done
例えば以下のような構成の場合、
- Infra repository:
git@github.com:libry-inc/test_cluster.git
- ディレクトリパス:
manifests/test
以下のコマンドを実行することでFluxのdeployができる。
fluxctl install \ --git-user=flux \ --git-email=flux@users.noreply.github.com \ --git-url=git@github.com:libry-inc/test_cluster.git \ --git-path=manifests/test \ --namespace=flux | kubectl apply -f -
さらに以下のコマンドでFluxのdeployが完了するのを待つことができ、
kubectl -n flux rollout status deployment/flux
successfully rolled out
と表示されたらFluxのdeploy完了となる。
もし今回指定したInfra repositoryがprivateの場合は、以下のコマンドを実行してFluxコンテナ側で生成したssh公開鍵をInfra repositoryのdeploy keyとして登録しておくと良い。
fluxctl identity --k8s-fwd-ns flux
上手く稼働していると、しばらく後にInfra repositoryに配置したmanifestsが適用されているのが確認できる。
$ kubectl get deployment NAME READY UP-TO-DATE AVAILABLE AGE sample-deployment 3/3 3 3 7m45s
secretsをGitHub上で管理する
GitOpsではGitHub上のコードがSingle Source of Truthである。
そのため、DBへの接続情報やAPI Keyなどの機密情報もGitHubで管理することになる。
Kubernetesのsecrets manifestsは、base64エンコードがかけられたYAMLなので容易に中身を読み解くことができる。
安全性を考えるのであれば、GitHubの権限とsecretsのような機密情報のアクセス権限は別のものとして管理する方が良いだろう。
そこで今回はAWS KMSを利用してsecretsを暗号化・復号化し、暗号化したものをGitHub上で管理していく方針を取ることにした。
sopsについて
AWS KMSを使って暗号化・復号化するにはsopsを使うと便利だ。
sopsを使って機密情報が含まれるファイルを暗号化すると、暗号化に使用したメタ情報とともに暗号化されたファイルが生成される。
今回はKubernetesのsecretsとして扱うので、それをさらにsecrets manifestsに変換することになる。
少し煩雑だが以下のような段階を踏むことになる。
- 平文の機密情報が含まれるファイル
- git管理しない
- sopsで暗号化したファイル
- git管理してもしなくてもいい
- 今回はInfra repositoryにsecretsというディレクトリを作成して、そこにgit管理している
- secrets manifests形式のYAMLファイル
- git管理する
- Fluxが監視しているディレクトリに配置しなければならない
sopsとKMSを使って暗号化し、secretsを作成する
今回は以下のファイルを暗号化してみる。
./path/to/test-secret
This is secret text.
AWS KMSに任意の鍵を作成し、それのARNを取得する。
sopsがインストールされ、KMSの鍵のアクセス権限を持つ環境で以下のコマンドを実行すると暗号化されたtest-secret.encが生成される。
sops -e --kms "arn:aws:kms:ap-northeast-1:XXXXXXXXXXXX:key/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" ./path/to/test-secret > ./path/to/test-secret.enc
メタ情報を持っているので、編集するときは以下のコマンドで済む。
sops ./path/to/test-secret.enc
またgitのdifferを設定すると、復号化した内容でdiffをみることもできるので、機密情報の変更履歴も追うことができる。
暗号化したファイルをさらにsecrets manifestsにするには以下のコマンドを実行する。
kubectl create secret generic test-secret-name --from-file test-secret.enc=./path/to/test-secret.enc --dry-run -o yaml > ./path/to/secrets.yaml
生成された./path/to/secrets.yamlをInfra repositoryのmanifestsが配置されているところに追加するとFluxが読み取ってsecretsリソースをEKS上に作成する。
ちなみにsecretsなどをイミュータブルにする場合は、test-secret.encのハッシュを計算し、test-secret-nameを置き換えるようなスクリプトを用意すると便利である。
EKS上から復号化する
復号化するために、EKSのサービスアカウントに今回のKMSの鍵のアクセス権限を付与する。
今回はkey-management-serviceというサービスアカウントを作っているので、以下のコマンドでサービスアカウントのIAM RoleのARNを取得することができる。
kubectl get serviceaccount/key-management-service -o jsonpath='{.metadata.annotations.eks\.amazonaws\.com/role-arn}'
取得したARNをKMSの鍵のキーユーザーとして紐づけるとEKS上のコンテナからKMSの鍵にアクセスすることができる。
実際に復号化を行う処理はInitContainerを使って記述すると良い。
先ほどのInfra repository上のdeployment.yamlを以下のように書き換える。
./path/to/deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: sample-deployment spec: selector: matchLabels: app: sample replicas: 3 template: metadata: labels: app: sample spec: serviceAccountName: key-management-service volumes: - name: secret-dir secret: secretName: test-secret-name - name: env-dir emptyDir: medium: Memory containers: - image: busybox:1.32.0 name: sample volumeMounts: - name: env-dir mountPath: /var/workspace/env command: - "/bin/sh" - "-c" - | cat /var/workspace/env/test-secret while : do date sleep 1m done initContainers: - name: decrypt-secret image: mozilla/sops:v3.6.0-alpine volumeMounts: - name: secret-dir mountPath: /root/workspace/secrets - name: env-dir mountPath: /root/workspace/env command: - "/bin/bash" - "-c" - | [ -d env/test-secret ] && exit 0 cd /root/workspace sops -d ./secrets/test-secret.enc > ./env/test-secret
変更内容をInfra repositoryに反映し、しばらくするとコンテナ上でtest-secretを復号化して読み込まれていることが確認できる。
$ kubectl logs pod/sample-deployment-XXXXXXXXX-XXXXX This is secret text. Sat Aug 1 07:57:40 UTC 2020 Sat Aug 1 07:58:40 UTC 2020
最後に
Libryでは現在、このようにしてGitOps構成図にあるような環境をスモールスタートではあるが運用しはじめている。
今回書くことができなかったCIの部分やこれからさらに取り組んでいくことなどまだまだこの領域には奥が深い内容が多くあり、フロントエンドエンジニアと称しながらも役割を超えて楽しく携わっている。
課題は多くあり、この運用が変わる可能性は十分にあるが、少しでもLibryがどんな技術を扱っているのか参考になれば幸いである。