CDKでEKSクラスタの作成からHelm ChartでのLocustのインストールまでを一気に行う

awskubernetes

以前、Helm で locust をインストールしたが、今回は EKS に CDK でインストールする。CDK だとクラスタの作成からできるので cdk deploy で一気に環境が整う。

KubernetesにHelmでLocustによる分散負荷試験環境を立てる - sambaiz-net

$ npm run cdk -- --version
1.62.0 (build 8c2d7fc)

まず VPC と Cluster を作成する。mastersRole をユーザーが assume して kubectl コマンドを実行できるように Principal に AWS アカウントも入れている。

AWSのAssumeRole - sambaiz-net

その後、実行するタスクを記述したスクリプト locustfile の ConfigMap を作成し、 これを Chart の worker.config.configmapName で参照する。キー名を間違えがち。 Chart のリポジトリは Helm Hub のもの。

追記 (2020-12-21): 以前はHelm Hubの https://kubernetes-charts.storage.googleapis.com/ を参照していたが、helm/charts リポジトリが deprecated になり削除されてしまったので、archiveを参照するようにした。

追記 (2022-12-31): 今は AmazonEKSServicePolicy を紐づける必要はない

import * as cdk from '@aws-cdk/core';
import { Cluster, KubernetesVersion, DefaultCapacityType } from '@aws-cdk/aws-eks'
import { Vpc, SubnetType, InstanceType } from '@aws-cdk/aws-ec2'
import { Role, ManagedPolicy, ServicePrincipal, AccountPrincipal, CompositePrincipal } from '@aws-cdk/aws-iam'
import * as fs from 'fs';

export class TryEksStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    const vpc = new Vpc(this, 'vpc', {
      cidr: '10.0.0.0/16',
      maxAzs: 2,
      subnetConfiguration: [
        {
          cidrMask: 18,
          name: 'public',
          subnetType: SubnetType.PUBLIC,
        },
        {
          cidrMask: 18,
          name: 'private',
          subnetType: SubnetType.PRIVATE,
        },
      ]
    })
    cdk.Tags.of(vpc).add("Name", this.stackName)
    const mastersRole = new Role(this, 'masters-role', {
      assumedBy: new CompositePrincipal(
        new ServicePrincipal('eks.amazonaws.com'),
        new AccountPrincipal(this.account)
      ),
      managedPolicies: [
        ManagedPolicy.fromAwsManagedPolicyName('AmazonEKSClusterPolicy'),
        ManagedPolicy.fromAwsManagedPolicyName('AmazonEKSServicePolicy'),
      ]
    })
    const cluster = new Cluster(this, 'cluster', {
      vpc,
      mastersRole,
      clusterName: '<cluster_name>',
      version: KubernetesVersion.V1_17,
      defaultCapacity: 2,
      defaultCapacityInstance: new InstanceType('m5.large')
    })
    const configmap = {
      apiVersion: 'v1',
      kind: 'ConfigMap',
      metadata: {
        name: 'locust-worker-configs',
      },
      data: {
        'tasks.py': fs.readFileSync('lib/tasks.py').toString(),
      },
    }
    cluster.addManifest('locust-worker-configmap', configmap)
    cluster.addChart('locust-chart', {
      chart: 'locust',
      repository: 'https://charts.helm.sh/stable',
      version: '1.2.1',
      wait: true,
      values: {
        master: {
          config: {
            'target-host': 'https://example.com'
          }
        },
        worker: {
          replicaCount: 2,
          config: {
            configmapName: configmap.metadata.name,
          }
        }
      }
    })
  }
}

イメージのバージョンを最新の 1.x 系にしたところ、 現状のChartが設定する LOCUSTFILE_PATH を読んでくれず Could not find any locustfile! になってしまったのでデフォルトの 0.9 で動かしている。 locustfile の書き方が少し異なるのでバージョンを上げたい場合は Chart を持ってきて編集するか自前のイメージを参照する。

$ cat lib/tasks.py
from locust import HttpLocust, TaskSet, task

class MyTaskSet(TaskSet):
  @task
  def index(self):
    self.client.get("/")

class MyUser(HttpLocust):
  task_set = MyTaskSet
  min_wait = 5
  max_wait = 15

デプロイすると update-kubeconfig するコマンドが Outputs に出るので実行する。Chart がインストールされ Locust が起動していることが確認できる。

$ npm run cdk -- deploy
...
Outputs:
TryEksStack.clusterConfigCommand30DB378E = aws eks update-kubeconfig --name <cluster_name> --region <region> --role-arn <masters_role_arn>
TryEksStack.clusterGetTokenCommand09C244B5 = aws eks get-token --cluster-name <cluster_name> --region <region> --role-arn <masters_role_arn>

$ $(aws cloudformation describe-stacks --region <region> --stack-name <stack_name> | jq -r '.Stacks | .[] | .Outputs | reduce .[] as $i ({}; .[$i.OutputKey] = $i.OutputValue) | to_entries | map(select(.key | startswith("clusterConfigCommand"))) | .[0].value')

$ kubectl config view
apiVersion: v1
...
current-context: <cluster_arn>
kind: Config
preferences: {}
users:
- name: <cluster_arn>
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1alpha1
      args:
      - --region
      - <region>
      - eks
      - get-token
      - --cluster-name
      - <cluster_name>
      - --role
      - <masters_role>
      command: aws
      env:
      - name: AWS_PROFILE
        value: <profile_name>

$ helm list
NAME                                            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION     
tryeksstackclusterchartlocustchart1abdd876      default         1               2020-09-16 07:51:53.672161109 +0000 UTC deployed        locust-1.2.1    0.9.0   

$ kubectl get pod
NAME                                                              READY   STATUS    RESTARTS   AGE
tryeksstackclusterchartlocustchart1abdd876-master-7fd8998cn4w8z   1/1     Running   0          4m9s
tryeksstackclusterchartlocustchart1abdd876-worker-58b74b6bsgctk   1/1     Running   1          4m9s
tryeksstackclusterchartlocustchart1abdd876-worker-58b74b6bsltvr   1/1     Running   0          4m9s

$ kubectl port-forward $(kubectl get pod -l "component=master" --template '{{(index .items 0).metadata.name}}') 8089
$ open http://localhost:8089

次のようなエラーが出た場合、bootstrapして CDKTookKit stackをデプロイする。

Error: This stack uses assets, so the toolkit stack must be deployed to the environment
$ npm run cdk -- bootstrap

後片付けする。

$ npm run cdk -- destroy

EKS上のLocustから負荷をかける際のリソースの割り当てやインスタンスタイプの調整 - sambaiz-net

CDKでECS(EC2)上にLocust masterとworkerのServiceをデプロイしCloud Mapで名前解決させる - sambaiz-net