GeekFactory

int128.hatenablog.com

IAM Roles for Service Accountsに必要なリソースを作成するTerraformモジュールを書いた

kopsなどで自前構築しているKubernetesクラスタでIAM Roles for Service Accounts(IRSA)を利用する時に必要なAWSリソースを作成するTerraformモジュールを書きました。

github.com

kopsとTerraformの組み合わせでIRSAを利用する方法は下記の記事を参考にさせていただきました。ありがとうございます。

blog.hatappi.me

h3poteto.hatenablog.com

Terraformモジュールの使い方

TerraformモジュールのREADME.mdに使い方を書いています。本記事に日本語で使い方を書いておきます。

鍵ペアの生成

まず、鍵ペアを生成します。

mkdir -p irsa
cd irsa

ssh-keygen -t rsa -b 2048 -f sa-signer.key -m pem
ssh-keygen -e -m PKCS8 -f sa-signer.key.pub > sa-signer-pkcs8.pub

go run /amazon-eks-pod-identity-webhook/hack/self-hosted/main.go -key sa-signer-pkcs8.pub | jq '.keys += [.keys[0]] | .keys[1].kid = ""' > jwks.json

Terraformの実行

Terraformで kubernetes-irsa モジュールを適用します。

resource "random_uuid" "irsa_s3_bucket_name" {
}

module "irsa" {
  source              = "int128/kubernetes-irsa/aws"
  oidc_s3_bucket_name = "oidc-${random_uuid.irsa_s3_bucket_name.result}"
  oidc_jwks_filename  = "./irsa/jwks.json"
}

output "irsa_oidc_issuer" {
  description = "Issuer of OIDC provider for IRSA"
  value       = module.irsa.oidc_issuer
}

output "irsa_pod_identity_webhook_ecr_repository_url" {
  description = "URL to the ECR repository for eks/pod-identity-webhook"
  value       = module.irsa.pod_identity_webhook_ecr_repository_url
}

resource "local_file" "irsa_kops_cluster_yaml" {
  filename = "./irsa/kops.yaml"
  content  = module.irsa.kops_cluster_yaml
}

このTerraformモジュールを実行すると、下記のAWSリソースが作成されます。

https://raw.githubusercontent.com/int128/terraform-aws-kubernetes-irsa/c21a21bccf8b8486c2d43254725c51a2f165da3a/diagram.svg

このTerraformモジュールで作成されるCodeBuildプロジェクトを実行すると、CodeBuild上で amazon-eks-pod-identity-webhook のDockerイメージをビルドしてECRリポジトリにpushできます。ローカルでビルドする手間が省けます。

kopsの設定

続いて、kopsのクラスタ設定を変更します。Terraformを実行すると以下の内容で kops.yaml が生成されるので、{base64} の部分を実際の鍵に置き換えます。

spec:
  fileAssets:
  - content: {base64 sa-signer.key}
    isBase64: true
    name: service-account-signing-key-file
    path: /srv/kubernetes/assets/service-account-signing-key
  - content: {base64 sa-signer-pkcs8.pub}
    isBase64: true
    name: service-account-key-file
    path: /srv/kubernetes/assets/service-account-key
  kubeAPIServer:
    apiAudiences:
    - sts.amazonaws.com
    serviceAccountIssuer: https://oidc-RANDOM.s3.amazonaws.com
    serviceAccountKeyFile:
    - /srv/kubernetes/server.key
    - /srv/kubernetes/assets/service-account-key
    serviceAccountSigningKeyFile: /srv/kubernetes/assets/service-account-signing-key
base64 irsa/sa-signer-pkcs8.pub | sed -e 's/=//g'
base64 irsa/sa-signer.key | sed -e 's/=//g'

kops.yaml ファイルの内容をkopsに適用します。

kops edit cluster

pod-identity-webhookのデプロイ

amazon-eks-pod-identity-webhook リポジトリMakefileを実行して、Kubernetesクラスタにpod-identity-webhookをデプロイします。

cd /amazon-eks-pod-identity-webhook
make cluster-up IMAGE=REGISTRY_ID.dkr.ecr.REGION.amazonaws.com/eks/pod-identity-webhook

動作確認

最終的に、Service Accountを割り当てたPodがS3バケットにアクセスできるか確認します。

まず、S3バケットにアクセスするためのIAMロールを作成します。

resource "aws_iam_role" "s3-echoer" {
  name               = "s3-echoer"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "${module.irsa.oidc_provider_arn}"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "${module.irsa.oidc_issuer}:sub": "system:serviceaccount:default:s3-echoer"
        }
      }
    }
  ]
}
EOF
}

resource "aws_iam_role_policy_attachment" "s3-echoer" {
  role       = "${aws_iam_role.s3-echoer.name}"
  policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
}

Service Accountを作成します。先ほど作成したIAMロールのARNをアノテーションに含めます。

kubectl create sa s3-echoer
kubectl annotate sa s3-echoer eks.amazonaws.com/role-arn=arn:aws:iam::ACCOUNT_ID:role/s3-echoer

aws-cliを実行してS3バケットのリストが表示されることを確認します。

kubectl run aws -i --rm --image amazon/aws-cli --restart=Never --serviceaccount=s3-echoer -- s3 ls

まとめ

kopsなどで自前構築しているKubernetesクラスタでIAM Roles for Service Accounts(IRSA)を利用する時に必要なAWSリソースを作成するTerraformモジュールを書きました。面倒な構築作業を簡略化できるので、役に立ったら幸いです。

Prometheus Alertmanagerの通知テンプレートを改善する

AlertmanagerのSlack通知テンプレートで四苦八苦したのでメモを残します。

Prometheus OperatorのHelm chartには便利なデフォルトルールが組み込まれています。例えば、Podが頻繁に再起動している場合に通知するルール(KubePodCrashLooping)が組み込まれています。しかし、Slackやメールなどのアラート通知は自分で設定する必要があります。

アラート通知の書き方は公式の Notification template examples | Prometheus に例がありますが、そのままではうまくメッセージが表示されない場合があります。ドキュメントでは以下の例が挙げられていますが、 summarydescription というアノテーションが付いているルールでないとメッセージが空白になってしまいます。また、CommonAnnotations を使っているため、複数のアラートがグループ化された場合は共通のアノテーションしか表示されません。

- name: 'team-x'
  slack_configs:
  - channel: '#alerts'
    # Alertmanager templates apply here.
    text: "<!channel> \nsummary: {{ .CommonAnnotations.summary }}\ndescription: {{ .CommonAnnotations.description }}"

通知テンプレートのよい例はないか探したところ、下記の記事が参考になりました。

medium.com

Prometheus Operatorのデフォルトルールには message というアノテーションが付いているため、テンプレートでは message を使うとよさそうです。また、グループ化された場合にうまく表示されない問題は Alerts フィールドに入っているアラートを列挙すると解決できそうです。

というわけで試行錯誤の結果、以下のテンプレートを利用しています。

alertmanager:
  config:
    receivers:
      - name: "null"
      - name: slack
        slack_configs:
          - api_url: https://slack.example.com/webhook
            channel: "#alerts"
            send_resolved: true
            username: dev/Alertmanager
            icon_url: https://prometheus.io/assets/favicons/favicon.ico
            title: |
              {{ .Status | toUpper }}
            text: |
              {{- range .Alerts }}
              - **{{ .Labels.alertname }}**: {{ .Annotations.message }}
              {{- end }}
    route:
      receiver: "null"
      routes:
        - receiver: "null"
          match:
            alertname: Watchdog
        - receiver: slack

Fluxによるアプリケーションの継続的デプロイ

FluxのAutomated deployment of new container imagesを利用して、Kubernetes上でアプリケーションの継続的デプロイを構成する機会があったのでまとめます。

GitOpsの基本形

GitOpsを採用する場合は下図のデプロイフローが基本形になります。

https://raw.githubusercontent.com/int128/continuous-deployment-flux-demo/master/gitops-basic-flow.svg?sanitize=true

具体的には以下の流れになります。

  1. 開発者がアプリケーションリポジトリを更新する。
  2. CIがアプリケーションをビルドし、新しいDockerイメージをプッシュする。
  3. 開発者がマニフェストリポジトリを更新する。
  4. GitOpsが新しいマニフェストをデプロイする。

これはWeaveworksのGuide To GitOpsで説明されている開発フローです。チームの大きさやアプリケーションの特性によっては、以下のような違いが出てくると思います。

継続的デプロイに必要なもの

チームが小さい、あるいは、チームが自己完結している場合は、アプリケーションを更新した契機で開発環境にデプロイされる方が効率的に開発できることがあります。ビジネス上の受入テストまで自動化している場合は本番環境まで自動デプロイすることもあります。これを継続的デプロイ(Continuous Deployment)といいます。下記の記事が参考になります。

blog.kyanny.me

継続的デプロイを実現するにはアプリケーションのビルドとデプロイを連続して行う必要があります。Cloud FoundryのようなPaaSを利用するとこのような開発フローを簡単に実現できますが、KubernetesのようなCaaSでは仕組みを作る必要があります。具体的には、以下が自動的に流れる仕組みが必要になります。

  1. 開発者がアプリケーションリポジトリを更新する。
  2. CIがアプリケーションをビルドし、新しいDockerイメージをプッシュする。
  3. (何らかの仕組みで)マニフェストリポジトリにあるマニフェストのイメージタグを書き換える。
  4. GitOpsが新しいマニフェストクラスタに適用する。

今のところ、GitOpsで継続的デプロイを実現するにはFluxを利用するか、上記3を自作する必要があります。Argo CDでは実現できません。

Flux automated deployment of new container images

FluxにはAutomated deployment of new container imagesという機能があります。この機能を有効にすると、Dockerレジストリに新しいイメージが存在したらマニフェストのイメージタグを新しいものに書き換えてくれます。ちょうど下図のようなデプロイフローになります。

https://raw.githubusercontent.com/int128/continuous-deployment-flux-demo/master/gitops-continuous-deployment-flow.svg?sanitize=true

具体的には以下の流れになります。

  1. Gitリポジトリからマニフェストを取得する。
  2. Dockerレジストリにあるイメージをスキャンする。
  3. 新しいタイムスタンプのイメージが存在する場合は以下を行う。
    1. マニフェストに書かれているイメージタグを新しいものに書き換える。
    2. Gitリポジトリに新しいコミットをpushする。
  4. 新しいマニフェストクラスタに適用する。

公式ドキュメントには詳細が書かれていないので、Fluxのログを元に動作を推測しています。間違っていたら教えてください。

Automated deployment of new container imagesを有効にするには、Deploymentに以下のアノテーションを付加します。

metadata:
  annotations:
    fluxcd.io/automated: "true"

スキャン対象のイメージタグを正規表現やglobで絞り込むことも可能です。以下に例を示します。

metadata:
  annotations:
    fluxcd.io/automated: "true"
    # appコンテナについて、master-で始まるイメージタグのみ自動更新とする
    fluxcd.io/tag.app: glob:master-*
spec:
  template:
    spec:
      containers:
      - name: app
        image: ...

スキャン対象のイメージ名やDockerレジストリを限定することも可能です。詳しくはfluxdの引数リストを参照してください。

下記のリポジトリにデモを置いているので、よかったらご覧ください。

github.com

FlaggerによるCanary Release

参考までに、FluxとFlaggerを併用するとCanary Releaseが可能です。アプリケーションリポジトリを更新した契機でデプロイを開始し、一定の基準(例:10%のトラフィックに新しいバージョンを適用して99%が成功ステータスだった場合)を満たしたら全トラフィックを新しいバージョンに切り替える、といったことも可能です。

github.com

Flaggerを使うにはIstioなどのサービスメッシュが必要です。また、Kialiなどの可視化ツールがあるとトラフィックの切り替えがよく分かります。詳しくは下記の記事が参考になります。

medium.com

Fluxによる継続的デプロイの課題

Fluxには以下の制約があるため、実際に運用してみると辛いところがあります。

  • デプロイが成功した場合やエラーが発生した場合の通知がない。
    • WeaveworksとしてはWeave Cloudを使ってほしいみたいです。
    • fluxdのログをelastalertなどで監視してSlackに通知する仕組みを作れば可能です。
    • デプロイが成功するとflux-syncタグが更新されるので、GitHubでタグの更新をSlackに通知するという方法もあります。
    • FlaggerにはSlack通知の機能があります。
  • GUIがないので、クラスタで何が起きているのか分かりにくい。
    • クラスタで何が起きているのか確認するには以下の方法があります。
      • fluxdのログを見る。
      • fluxctlコマンドでDeploymentのステータスを見る。
      • Prometheusでfluxdのステータスを取得する。
    • Argo CDのような使うやすいGUIはありません。
  • 同期状態を管理するためにマニフェストリポジトリflux-syncというタグが作成される。
    • flux-syncタグ契機でCIが実行されないように設定するとよいです。

参考までに、fluxctlコマンドを利用すると以下のようなステータスを表示できます。

% fluxctl --k8s-fwd-ns flux -n develop list-images
WORKLOAD                         CONTAINER   IMAGE                                                                  CREATED
develop:deployment/echoserver    echoserver  gcr.io/google_containers/echoserver
                                             '-> 1.10                                                               22 Mar 18 17:30 UTC
                                                 1.9                                                                14 Feb 18 22:35 UTC
                                                 1.8                                                                27 Jul 17 18:50 UTC
                                                 1.7                                                                18 Jul 17 19:23 UTC
                                                 1.6                                                                16 Jun 17 22:41 UTC
                                                 1.5                                                                06 Jun 17 23:28 UTC
                                                 1.4                                                                29 May 16 05:03 UTC
                                                 1.3                                                                08 Mar 16 19:10 UTC
                                                 1.2                                                                23 Feb 16 02:18 UTC
                                                 1.1                                                                26 Jan 16 18:13 UTC

デプロイの前後で自動テストなどの処理を入れたい場合は今のところ方法がありません。Tekton CDでは柔軟なデプロイメントパイプラインを構成できるので、今後Tekton CDのエコシステムが発達してきたら有力候補になるかもしれませんね。