AWS CDKでEKSクラスタを作ってみた
AWS CDKが今日も楽しいです。CSDKで触ったことがなかったEKSクラスタを作成しました。
本記事ではAWS CDKを使って薄くEKSクラスタをどのように作ったかを記載します。 TypeScriptやAWS CDKの基本的な部分は記載しません。 また、本ブログで使用した、AWS CDKのバージョンは1.4.0です。
プロジェクトの初期設定
AWS CDKのスタックを作成するための初期設定を行ないます。
個人の趣向ですが、cdk init
をせずにプロジェクトの設定を行います。
$ mkdir cdk-eks-101 && cd $_ $ mkdir src $ git init $ npm init -y $ npm i @aws-cdk/{core,aws-eks,aws-ec2,aws-iam} $ npm i -D aws-cdk @types/node typescript
次に、package.json
の設定をしてます。
下記のscriptsの部分を変更してください。
{ "name": "cdk-eks-101", "version": "1.0.0", "description": "", "scripts": { "build": "tsc", "watch": "tsc -w", "deploy": "tsc && cdk deploy" }, "author": "37108", "license": "MIT", "devDependencies": { "@types/node": "^12.7.2", "aws-cdk": "^1.4.0", "typescript": "^3.5.3" }, "dependencies": { "@aws-cdk/aws-ec2": "^1.4.0", "@aws-cdk/aws-eks": "^1.4.0", "@aws-cdk/aws-iam": "^1.4.0", "@aws-cdk/core": "^1.4.0" } }
ハイライトした部分の変更はできましたか。author
やLICENSE
などの細かい部分は人によってまちまちなのであまり気にしないでください。
次に、tsconfig.json
を作成します。
あまりルールを厳しくはしていないのでお好みで変えてください。
{ "compilerOptions": { "target": "ES2018", "module": "commonjs", "outDir": "./dist/", "lib": [ "es2016", "es2017.object", "es2017.string" ], "noUnusedLocals": false, "noUnusedParameters": false } }
最後にcdk.json
を追加して初期設定は終わりです。
{ "app": "node dist/index" }
これでプロジェクトの初期設定は終了です。
CDKのコーディング
コードを書いて、EKSクラスタを作成します。
クラスの作成
まずはクラスを作成します。
いつも通り、cdk.Stackクラスを継承して新規クラスを作成します。
作成したクラスを元にインスタンスを作成する際に、region
を渡してください。
現在のバージョンでは、EKSクラスタを作成する場合は指定しないとCDKスタックの作成に失敗します。
実際にここを指定しないで、CDKスタックを作成しようとすると、Unable to determine AMI from AMI map since stack is region-agnostic
とエラーがでます。
#!/usr/bin/env node import cdk = require('@aws-cdk/core') export class EKS101Stack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props) } } const app = new cdk.App() new EKS101Stack(app, 'EKS101Stack', { env: { region: 'ap-northeast-1' } }) app.synth()
VPCの作成
EKSクラスタのノードが稼働するためのVPCと、サブネットを作成します。
#!/usr/bin/env node import cdk = require('@aws-cdk/core') import { Vpc, InstanceType, SubnetType } from '@aws-cdk/aws-ec2' export class EKS101Stack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props) const vpc = new Vpc(this, 'vpc', { cidr: '192.168.0.0/16', natGateways: 1, subnetConfiguration: [ { cidrMask: 24, name: 'Public1', subnetType: SubnetType.PUBLIC, }, { cidrMask: 24, name: 'Public2', subnetType: SubnetType.PUBLIC, }, { cidrMask: 24, name: 'Private1', subnetType: SubnetType.PRIVATE, }, { cidrMask: 24, name: 'Private2', subnetType: SubnetType.PRIVATE, }, ] }) } } const app = new cdk.App() new EKS101Stack(app, 'EKS101Stack', { env: { region: 'ap-northeast-1' } }) app.synth()
IAM ロールの作成
EKSクラスタのマスタノード用にアタッチするIAM ロールを作成します。
実際に本番環境で作成する場合は権限などを十分に検討して作成してください。
#!/usr/bin/env node import cdk = require('@aws-cdk/core') import { Vpc, InstanceType, SubnetType } from '@aws-cdk/aws-ec2' import { Role, ServicePrincipal, ManagedPolicy } from '@aws-cdk/aws-iam' export class EKS101Stack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props) const vpc = new Vpc(this, 'vpc', { cidr: '192.168.0.0/16', natGateways: 1, subnetConfiguration: [ { cidrMask: 24, name: 'Public1', subnetType: SubnetType.PUBLIC, }, { cidrMask: 24, name: 'Public2', subnetType: SubnetType.PUBLIC, }, { cidrMask: 24, name: 'Private1', subnetType: SubnetType.PRIVATE, }, { cidrMask: 24, name: 'Private2', subnetType: SubnetType.PRIVATE, }, ] }) const eksRole = new Role(this, 'eksRole', { assumedBy: new ServicePrincipal('eks.amazonaws.com') }) eksRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('AmazonEKSClusterPolicy')) eksRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('AmazonEKSServicePolicy')) } } const app = new cdk.App() new EKS101Stack(app, 'EKS101Stack', { env: { region: 'ap-northeast-1' } }) app.synth()
Deployment、Serviceの定義
EKSクラスタで使用する、Deployment、Serviceを定義します。
可読性を考慮して、別ファイルに出します。
const appLabel = { app: "hello-kubernetes" } export const deployment = { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "hello-kubernetes" }, spec: { replicas: 3, selector: { matchLabels: appLabel }, template: { metadata: { labels: appLabel }, spec: { containers: [ { name: "hello-kubernetes", image: "paulbouwer/hello-kubernetes:1.5", ports: [ { containerPort: 8080 } ] } ] } } } } export const service = { apiVersion: "v1", kind: "Service", metadata: { name: "hello-kubernetes" }, spec: { type: "LoadBalancer", ports: [ { port: 80, targetPort: 8080 } ], selector: appLabel } }
クラスタの作成
EKSクラスタを作成して、先ほど作成したDeployment、Serviceを追加していきます。
clusterに対して、addCapacityでEC2インスタンスを追加していますが、デフォルトで2台EC2インスタンスが作成されるので合計4台のインスタンスが起動されます。
多いという場合は、addCapacityを使わないか、clusterの定義時に、デフォルトの台数を0にするとよいでしょう。
また、Auto Scalingも使用可能なので、ユースケースに合わせて対応することが可能です。
#!/usr/bin/env node import cdk = require('@aws-cdk/core') import { Vpc, InstanceType, SubnetType } from '@aws-cdk/aws-ec2' import { Role, ServicePrincipal, ManagedPolicy } from '@aws-cdk/aws-iam' import { Cluster } from '@aws-cdk/aws-eks' import { deployment, service } from './manifest' export class EKS101Stack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props) const vpc = new Vpc(this, 'vpc', { cidr: '192.168.0.0/16', natGateways: 1, subnetConfiguration: [ { cidrMask: 24, name: 'Public1', subnetType: SubnetType.PUBLIC, }, { cidrMask: 24, name: 'Public2', subnetType: SubnetType.PUBLIC, }, { cidrMask: 24, name: 'Private1', subnetType: SubnetType.PRIVATE, }, { cidrMask: 24, name: 'Private2', subnetType: SubnetType.PRIVATE, }, ] }) const eksRole = new Role(this, 'eksRole', { assumedBy: new ServicePrincipal('eks.amazonaws.com') }) eksRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('AmazonEKSClusterPolicy')) eksRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('AmazonEKSServicePolicy')) const cluster = new Cluster(this, 'cluster', { vpc, mastersRole: eksRole, clusterName: 'boringWozniak', }) cluster.addCapacity('capacity', { desiredCapacity: 2, instanceType: new InstanceType('m5.large'), }) cluster.addResource('resource', deployment, service) } } const app = new cdk.App() new EKS101Stack(app, 'EKS101Stack', { env: { region: 'ap-northeast-1' } }) app.synth()
コーディングを振り返ってみると少ない記述でEKSクラスタが定義できていますね。
デプロイ
次はCDKスタックをデプロイしたいと思います。
CDK側の都合でS3バケットを作成する必要があるため、いきなりcdk deploy
をするとエラーがでます。
S3オブジェクトを確認したところ、EKS用に使用するためのPythonスクリプトのファイルが入ってました。
Bootstrapping
cdk deploy
の前に、Bootstrappingを行います。
これを行うと新たに、CDKToolkitというCFnスタックが作成され必要なファイルがS3に格納されます。
cdk destroy
ではこのCFnスタックは削除されないので注意してください。
$ npx cdk bootstrap aws:///ap-northeast-1 ⏳ Bootstrapping environment aws:///ap-northeast-1... CDKToolkit: creating CloudFormation changeset... ✅ Environment aws:///ap-northeast-1 bootstrapped.
Deploy
準備が終わったので、cdk deploy
を行います。
かなり時間がかかりますが、完了後にCFnスタックが確認できます。
$ npm run deploy (NOTE: There may be security-related changes not in this list. See http://bit.ly/cdk-2EhF7Np) Do you wish to deploy these changes (y/n)? y EKS101Stack: deploying... EKS101Stack: creating CloudFormation changeset... ✅ EKS101Stack
無事に出来上がりました。
確認
作成したリソースを確認してみます。
AWSマネジメントコンソールからEKSにアクセスしてみます。
無事にクラスタが出来上がってますね。
さいごに
クラスタノードに対して必要なIAM ロールをよしなに作ってくれたりSecurity Groupが必要分作成されたりと、少ない記述量でEKSクラスタができるという事実に驚愕しています。
今回は薄く実装しただけなので、もっと実用的に記載する機会があればと思ってます。