GeekFactory

int128.hatenablog.com

Terraformでcloud-configを記述する

AWSのEC2インスタンスでは、User Dataにシェルスクリプトを渡すことで起動時にスクリプトを実行できます。ファイルを配置したり、パッケージをインストールしたり、といった複雑なことがやりたい場合はcloud-initが便利です。詳しくは公式ドキュメントの例を参照してください。

docs.aws.amazon.com

例えば、下記のようなYAMLをUser Dataに指定すると、OSの起動時にsystemdで独自のサービスを開始できます。

#cloud-config
write_files:
  - path: /etc/systemd/system/helloworld.service"
    content: |
      [Unit]
      Description = Hello World
      [Service]
      ExecStart = /usr/local/bin/helloworld.sh
      Restart = always
      Type = simple
      [Install]
      WantedBy = multi-user.target
  - path: /usr/local/bin/helloworld.sh"
    permissions: 0755
    content: |
      #!/bin/sh
      while true; do date; sleep 1; done
runcmd:
  - ["systemctl", "start", "helloworld"]
  - ["systemctl", "enable", "helloworld"]

Terraformでcloud-configを指定する場合、下記のように外部ファイルを読み込むと簡単に書けます。

resource "aws_instance" "this" {
  user_data_base64 = filebase64('./cloud-config.yaml')
}

ただ、YAMLの中にインラインでシェルスクリプトなどをベタ書きしているため、可読性がよくないというデメリットがあります。エディタで開いてもsyntax highlightingしてくれません。

そこで、下記のように動的にYAMLを組み立てることで、シェルスクリプトなどのファイルを外部化できます。

resource "aws_instance" "this" {
  user_data_base64 = base64encode(join("\n", [
    "#cloud-config",
    yamlencode({
      write_files : [
        {
          path : "/etc/systemd/system/helloworld.service",
          content : file("./helloworld.service"),
        },
        {
          path : "/usr/local/bin/helloworld",
          content : file("./helloworld.sh"),
          permissions : "0755",
        },
      ],
      runcmd : [
        ["systemctl", "enable", "helloworld"],
        ["systemctl", "start", "helloworld"],
      ],
    })
  ]))
} 

サンプルが下記にあるので参考にどうぞ。

github.com

Homebrew FormulaをCircleCIでテストする

Homebrew Formulaは一度書くと変更する機会があまりないですが、外部からPRを受け付ける場合はCIでテストしておくと便利です。linuxbrew/linuxbrew イメージを利用すると、LinuxのCI runnerでもbrewコマンドを利用できます。

CircleCIの場合は下記の設定でFormulaをテストできます。

# .circleci/config.yml
version: 2
jobs:
  build:
    docker:
      - image: linuxbrew/linuxbrew:1.9.3
    environment:
      HOMEBREW_NO_AUTO_UPDATE: 1
    steps:
      - checkout
      - run: brew install foobar.rb
      - run: brew test foobar

この例ではリポジトリにある foobar.rb というFormulaをテストしています。デフォルトでは Updating Homebrew... でめっちゃ待たされるので HOMEBREW_NO_AUTO_UPDATE を設定しています。

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モジュールを書きました。面倒な構築作業を簡略化できるので、役に立ったら幸いです。