はじめに
前回の記事 では Pulumi の簡単なセットアップと使用方法などをまとめてみました。
今回は AWS 上に EKS のクラスタと kubernetes の deployment と service を構築する際に Pulumi を使用してみました。
今回のサンプルコードは GitHub にあるので良ければ参考にしてみてください。
概要
今回は以下のような構成で作成していきます。
EKS クラスターの仮想ネットワーク
EKS クラスターの前提条件として、2 つ以上の異なる AZ のパブリックサブネットが必要です。
そのため、東京リージョン(ap-northeast-1
)上にap-northeast-1a
, ap-northeast-1c
の AZ 上にサブネットをそれぞれ 1 つずつ作成しました。
今回の構成ではデータベースがないため、プライベートサブネットは用意していません。
EKS クラスターの権限周り
EKS クラスターと EKS ワーカーノード用 それぞれに、 eksClusterRole
, eksWorkerRole
という 2 つの IAM ロールを用意します。
また、それぞれに以下の IAM Policy を設定します。
参考 - AWS 公式ドキュメント AmazonEKSClusterPolicy
クラスターを作成する前に、このポリシーをアタッチしたクラスター IAM ロールが必要となります。
参考 - AWS 公式ドキュメント AmazonEKS_CNI_Policy
AmazonEKS_CNI_Policy ポリシーを IAM エンティティにアタッチできます。Amazon EC2 ノードグループを作成する前に、このポリシーをノード IAM ロール、または AWS VPC CNI プラグインで使用する IAM ロールにアタッチする必要があります
参考 - AWS 公式ドキュメント AmazonEKSWorkerNodePolicy
このポリシーを、Amazon EKS がユーザーに代わってアクションを実行することを許可する Amazon EC2 ノードを作成するときに指定するノード IAM ロールにアタッチしなければなりません。
eksClusterRole
AmazonEKSClusterPolicy
Kubernetes クラスターが AWS のサービスを呼び出す際に必要
eksWorkerRole
AmazonEKSWorkerNodePolicy
インスタンスボリュームとネットワーク情報の読み取り、ワーカーノードからの EKS クラスターアクセス用AmazonEKS_CNI_Policy
ネットワーク設定変更用AmazonEC2ContainerRegistryReadOnly
ECR のイメージを参照するために必要
コードに関して説明
実際のコードになります。
GitHub - warawara28/pulumi-sample-aws-eks-cluster: AWS EKS Cluster sample with Pulumi
サンプルコードでは aws
と kubernetes
という 2 つのプロジェクトに分けていて以下のように分離しています。
aws
AWS のリソース (VPC やサブネットなどのネットワーク、EKS クラスターととワーカーノードが含まれる)kubernetes
k8s のマニフェスト
AWS のリソース
ネットワーク周りの作成
ネットワーク関連で以下のリソースを作成しています。
Vpc
1 つSubnet
2 つ- インターネット接続用の
InternetGateway
1 つ - それぞれのサブネットに対応する
RouteRable
とRouteTableAssociation
それぞれ 2 つずつ
const vpcName: string = `${projectName}-vpc`;
const vpc = new aws.ec2.Vpc(vpcName, {
cidrBlock: '10.0.0.0/16',
tags: {
Name: vpcName,
},
});
const internetGatewayName: string = `${projectName}-internet-gateway`;
const internetGateway = new aws.ec2.InternetGateway(internetGatewayName, {
vpcId: vpc.id,
tags: {
Name: internetGatewayName,
},
});
const subnetNameA = `${projectName}-subnet-a`;
const subnetA = new aws.ec2.Subnet(subnetNameA, {
vpcId: vpc.id,
availabilityZone: 'ap-northeast-1a',
cidrBlock: '10.0.1.0/24',
mapPublicIpOnLaunch: true,
tags: {
Name: subnetNameA,
},
});
const subnetNameC = `${projectName}-subnet-c`;
const subnetC = new aws.ec2.Subnet(subnetNameC, {
vpcId: vpc.id,
availabilityZone: 'ap-northeast-1c',
cidrBlock: '10.0.2.0/24',
mapPublicIpOnLaunch: true,
tags: {
Name: subnetNameC,
},
});
const routetableNameA: string = `${subnetNameA}-routetable`;
const routetableA = new aws.ec2.RouteTable(routetableNameA, {
vpcId: vpc.id,
routes: [
{
cidrBlock: '0.0.0.0/0',
gatewayId: internetGateway.id,
},
],
tags: {
Name: routetableNameA,
},
});
const routetableNameC: string = `${subnetNameC}-routetable`;
const routetableC = new aws.ec2.RouteTable(routetableNameC, {
vpcId: vpc.id,
routes: [
{
cidrBlock: '0.0.0.0/0',
gatewayId: internetGateway.id,
},
],
tags: {
Name: routetableNameC,
},
});
const routeTableAssociationA = new aws.ec2.RouteTableAssociation(
`${subnetNameA}-rta`,
{
subnetId: subnetA.id,
routeTableId: routetableA.id,
}
);
const routeTableAssociationC = new aws.ec2.RouteTableAssociation(
`${subnetNameC}-rta`,
{
subnetId: subnetC.id,
routeTableId: routetableC.id,
}
);
EKS クラスタとノードグループの作成
EKS クラスタで以下のリソースを作成しています。
Role
2 つeksClusterRole
eksWorkerRole
Cluster
1 つNodeGroup
1 つ
今回は t3.micro
のインスタンスを 2 つ用意した NodeGroup を用意します。
const eksClusterRole = new aws.iam.Role(`${projectName}-eks-cluster-role`, {
assumeRolePolicy: JSON.stringify({
Version: '2012-10-17',
Statement: [
{
Action: 'sts:AssumeRole',
Effect: 'Allow',
Sid: '',
Principal: {
Service: 'eks.amazonaws.com',
},
},
],
}),
});
const eksClusterRoleAttach1 = new aws.iam.RolePolicyAttachment(
`role-policy-attachment-eks-cluster-policy`,
{
role: eksClusterRole.name,
policyArn: 'arn:aws:iam::aws:policy/AmazonEKSClusterPolicy',
}
);
const eksWorkerRole = new aws.iam.Role(`${projectName}-eks-worker-role`, {
assumeRolePolicy: JSON.stringify({
Version: '2012-10-17',
Statement: [
{
Action: 'sts:AssumeRole',
Effect: 'Allow',
Sid: '',
Principal: {
Service: 'ec2.amazonaws.com',
},
},
],
}),
});
const eksWorkerRoleAttach1 = new aws.iam.RolePolicyAttachment(
`role-policy-attachment-eks-worker-node-policy`,
{
role: eksWorkerRole.name,
policyArn: 'arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy',
}
);
const eksWorkerRoleAttach2 = new aws.iam.RolePolicyAttachment(
`role-policy-attachment-eks-cni-policy`,
{
role: eksWorkerRole.name,
policyArn: 'arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy',
}
);
const eksWorkerRoleAttach3 = new aws.iam.RolePolicyAttachment(
`role-policy-attachment-ecr-readonly-policy`,
{
role: eksWorkerRole.name,
policyArn: 'arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly',
}
);
const eksCluster = new aws.eks.Cluster(
`${projectName}-eks-cluster`,
{
roleArn: eksClusterRole.arn,
vpcConfig: {
subnetIds: [subnetA.id, subnetC.id],
},
},
{}
);
const nodeGroupName: string = `${projectName}-eks-nodegroup`;
const nodeGroup = new aws.eks.NodeGroup(
nodeGroupName,
{
clusterName: eksCluster.name,
nodeRoleArn: eksWorkerRole.arn,
subnetIds: [subnetA.id, subnetC.id],
scalingConfig: {
desiredSize: 2,
maxSize: 2,
minSize: 2,
},
nodeGroupName: nodeGroupName,
diskSize: 10,
instanceTypes: ['t3.micro'],
updateConfig: {
maxUnavailable: 1,
},
},
{}
);
k8s のリソース作成
kubernetes のリソースとして以下を作成します。
deployment
service
実際の運用では チームやサービスごとに namespace
を作成する場合もあると思いますが、今回は最小構成で作成します。
今回はイメージとして公式ドキュメントのサンプルアプリケーションをデプロイするでも使用されている public.ecr.aws/nginx/nginx:1.21
のイメージを使用します。
const deploymentName = `${serviceName}-deployment`;
const appLabels = { app: deploymentName };
const deployment = new k8s.apps.v1.Deployment(deploymentName, {
metadata: {
name: deploymentName,
labels: appLabels,
},
spec: {
replicas: 2,
selector: { matchLabels: appLabels },
template: {
metadata: { labels: appLabels },
spec: {
containers: [
{
name: deploymentName,
image: 'public.ecr.aws/nginx/nginx:1.21',
ports: [{ name: 'http', containerPort: 80 }],
imagePullPolicy: 'IfNotPresent',
},
],
},
},
},
});
const k8sServiceName = `${serviceName}-service`;
const service = new k8s.core.v1.Service(
k8sServiceName,
{
metadata: {
name: k8sServiceName,
labels: appLabels,
},
spec: {
type: 'LoadBalancer',
selector: appLabels,
ports: [{ protocol: 'TCP', port: 80, targetPort: 'http' }],
},
},
{
dependsOn: deployment,
}
);
実際に作成する
サンプルコードを元に aws
の方で記載されている AWS のリソースをデプロイします。
デプロイ前に npm install
でモジュールをインストールします。
> npm install
npm WARN deprecated [email protected]: The functionality that this package provided is now in @npmcli/arborist
npm WARN deprecated [email protected]: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
npm WARN deprecated [email protected]: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
added 124 packages, and audited 125 packages in 7s
32 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
その後 pulumi up
でデプロイします。
EKS のクラスタの作成にはおおよそ 10 分程かかります。
> pulumi up
Previewing update (dev)
View Live: https://~~~
Type Name Plan
+ pulumi:pulumi:Stack pulumi-sample-aws-dev create
+ ├─ aws:iam:Role pulumi-sample-aws-eks-cluster-role create
+ ├─ aws:ec2:Vpc pulumi-sample-aws-vpc create
+ ├─ aws:iam:Role pulumi-sample-aws-eks-worker-role create
+ ├─ aws:iam:RolePolicyAttachment role-policy-attachment-eks-cluster-policy create
+ ├─ aws:iam:RolePolicyAttachment role-policy-attachment-ecr-readonly-policy create
+ ├─ aws:iam:RolePolicyAttachment role-policy-attachment-eks-cni-policy create
+ ├─ aws:iam:RolePolicyAttachment role-policy-attachment-eks-worker-node-policy create
+ ├─ aws:ec2:InternetGateway pulumi-sample-aws-internet-gateway create
+ ├─ aws:ec2:Subnet pulumi-sample-aws-subnet-a create
+ ├─ aws:ec2:Subnet pulumi-sample-aws-subnet-c create
+ ├─ aws:eks:Cluster pulumi-sample-aws-eks-cluster create
+ ├─ aws:ec2:RouteTable pulumi-sample-aws-subnet-c-routetable create
+ ├─ aws:ec2:RouteTable pulumi-sample-aws-subnet-a-routetable create
+ ├─ aws:ec2:RouteTableAssociation pulumi-sample-aws-subnet-c-rta create
+ ├─ aws:ec2:RouteTableAssociation pulumi-sample-aws-subnet-a-rta create
+ └─ aws:eks:NodeGroup pulumi-sample-aws-eks-nodegroup create
Outputs:
+ eksClusterName: "pulumi-sample-aws-eks-cluster-ca4d43d"
Resources:
+ 17 to create
Do you want to perform this update? yes
Updating (dev)
View Live: https://~~~
Type Name Status
+ pulumi:pulumi:Stack pulumi-sample-aws-dev created
+ ├─ aws:iam:Role pulumi-sample-aws-eks-cluster-role created
+ ├─ aws:ec2:Vpc pulumi-sample-aws-vpc created
+ ├─ aws:iam:Role pulumi-sample-aws-eks-worker-role created
+ ├─ aws:iam:RolePolicyAttachment role-policy-attachment-eks-cluster-policy created
+ ├─ aws:iam:RolePolicyAttachment role-policy-attachment-eks-cni-policy created
+ ├─ aws:iam:RolePolicyAttachment role-policy-attachment-ecr-readonly-policy created
+ ├─ aws:iam:RolePolicyAttachment role-policy-attachment-eks-worker-node-policy created
+ ├─ aws:ec2:InternetGateway pulumi-sample-aws-internet-gateway created
+ ├─ aws:ec2:Subnet pulumi-sample-aws-subnet-a created
+ ├─ aws:ec2:Subnet pulumi-sample-aws-subnet-c created
+ ├─ aws:ec2:RouteTable pulumi-sample-aws-subnet-c-routetable created
+ ├─ aws:ec2:RouteTable pulumi-sample-aws-subnet-a-routetable created
+ ├─ aws:ec2:RouteTableAssociation pulumi-sample-aws-subnet-c-rta created
+ ├─ aws:ec2:RouteTableAssociation pulumi-sample-aws-subnet-a-rta created
+ ├─ aws:eks:Cluster pulumi-sample-aws-eks-cluster created
+ └─ aws:eks:NodeGroup pulumi-sample-aws-eks-nodegroup created
Outputs:
+ eksClusterName: "pulumi-sample-aws-eks-cluster-ca4d43d"
Resources:
+ 17 created
Duration: 12m46s
AWS のリソースをデプロイ後、Output に出力された クラスター名を確認しておきます。
今回は pulumi-sample-aws-eks-cluster-ca4d43d
になります。(リソース名に接尾辞としてランダムな英数字が付与されます。)
EKS のクラスター、ノードグループが作成されましたので、kubernetes の service
, deployment
を同様にデプロイします。
デプロイ前に README に書かれたように npm install
と kubeconfig
の作成を行います。
> npm install
npm WARN deprecated [email protected]: The functionality that this package provided is now in @npmcli/arborist
npm WARN deprecated [email protected]: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
npm WARN deprecated [email protected]: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
added 142 packages, and audited 143 packages in 20s
32 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
> aws eks update-kubeconfig --region ap-northeast-1 --profile pulumi-sample --name pulumi-sample-aws-eks-cluster-ca4d43d
Added new context arn:aws:eks:ap-northeast-1:xxxxxxxxxx:cluster/pulumi-sample-aws-eks-cluster-ca4d43d to ~~~
準備が完了したら、同様にpulumi up
でデプロイします。
> pulumi up
Previewing update (dev)
View Live: https://~~~
Type Name Plan
+ pulumi:pulumi:Stack pulumi-sample-service-dev create
+ ├─ kubernetes:apps/v1:Deployment pulumi-sample-service-deployment create
+ └─ kubernetes:core/v1:Service pulumi-sample-service-service create
Outputs:
+ loadBalancerHost: "ae9ad1a877e6d4fe5a952627ba6d7768-1194268529.ap-northeast-1.elb.amazonaws.com"
Resources:
+ 3 to create
Do you want to perform this update? yes
Updating (dev)
View Live: https://~~~
Type Name Status
+ pulumi:pulumi:Stack pulumi-sample-service-dev created
+ ├─ kubernetes:apps/v1:Deployment pulumi-sample-service-deployment created
+ └─ kubernetes:core/v1:Service pulumi-sample-service-service created
Outputs:
+ loadBalancerHost: "ae9ad1a877e6d4fe5a952627ba6d7768-1194268529.ap-northeast-1.elb.amazonaws.com"
Resources:
+ 3 created
Duration: 25s
kubernetes のデプロイ完了後に Output に出力されたホストにアクセスします。
https://ae9ad1a877e6d4fe5a952627ba6d7768-1194268529.ap-northeast-1.elb.amazonaws.com/
成功すれば以下のように nginx から返された HTML が表示されます。
作成後は料金がかからないように pulumi destroy
で削除します。
(以下の例の場合はスタックも削除しています。)
kubernetes
のリソースを削除した後に、 aws
のリソースを削除します。
> pulumi destroy
Previewing destroy (dev)
View Live: https://~~~
Type Name Plan
- pulumi:pulumi:Stack pulumi-sample-service-dev delete
- ├─ kubernetes:core/v1:Service pulumi-sample-service-service delete
- └─ kubernetes:apps/v1:Deployment pulumi-sample-service-deployment delete
Outputs:
- loadBalancerHost: "ae9ad1a877e6d4fe5a952627ba6d7768-1194268529.ap-northeast-1.elb.amazonaws.com"
Resources:
- 3 to delete
Do you want to perform this destroy? yes
Destroying (dev)
View Live: https://~~~
Type Name Status
- pulumi:pulumi:Stack pulumi-sample-service-dev deleted
- ├─ kubernetes:core/v1:Service pulumi-sample-service-service deleted
- └─ kubernetes:apps/v1:Deployment pulumi-sample-service-deployment deleted
Outputs:
- loadBalancerHost: "ae9ad1a877e6d4fe5a952627ba6d7768-1194268529.ap-northeast-1.elb.amazonaws.com"
Resources:
- 3 deleted
Duration: 25s
The resources in the stack have been deleted, but the history and configuration associated with the stack are still maintained.
If you want to remove the stack completely, run 'pulumi stack rm dev'.
> pulumi stack rm dev
This will permanently remove the 'dev' stack!
Please confirm that this is what you'd like to do by typing ("dev"): dev
Stack 'dev' has been removed
おわりに
今回は EKS 上でサンプルアプリケーションとして nginx のコンテナイメージをデプロイしてみました。
実際のサービスとして運用する際にはログ周りや監視、DB との接続 それらに加えて k8s のマニフェストの設定の修正 などが必要になってきます。
既存のサービスを IaC 化する際の第一歩としてこの記事が参考になれば幸いです。