はじめに
AWS環境構築をコード化するためのツールとしてAWS CDK、Terraform、Ansible、Pulumiなどがあります。
今回は記述言語をgolangとしてPulumiでEKS環境を構築します。
Pulumiとは
「Modern Infrastructure as Code」を掲げた、クラウドインフラを構成するためのOSSです。
対応言語はJavaScript、TypeScript、Python、golangなどが挙げられます。
GCP、AWS、Azureに対応しており、Kubernetesリソースをデプロイすることも可能です。
まだ新しいこともあり公式ドキュメントや公式サンプルコードはTypeScript中心となっています。
普段バックエンドを触っている人にとってはgolangの方が嬉しいですね。
環境情報
macOS Mojave 10.14.1
pulumi: 1.7.0
golang: 1.12.4
実装
golangを使用して実装します。
事前にPulumiやgolangはインストールされていることは前提とします。
$GOPATH/src/pulumi-eks
配下に以下のようにファイルを配置します。
├── Pulumi.yaml
├── main.go
├── iam.go
├── network.go
└── eks.go
Pulumi.yaml
プロジェクトのメタデータをPulumi.yaml
に記述します。
name: pulumi-eks
runtime: go
description: pulumi EKS
main.go
EKSリソースを作成するための関数呼び出しをmain.go
に記述します。
package main
import (
"github.com/pulumi/pulumi/sdk/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
err := createEks(ctx)
if err != nil {
return err
}
return nil
})
}
iam.go
EKSクラスタとEKSワーカーに付与するIAM Roleをiam.go
に記述します。
このIAM Roleを関数の返り値とし、EKSクラスタ作成時に使用します。
package main
import (
"encoding/json"
"github.com/pulumi/pulumi-aws/sdk/go/aws/iam"
"github.com/pulumi/pulumi/sdk/go/pulumi"
)
func createEksClusterRole(ctx *pulumi.Context, roleName string) (*iam.Role, error) {
assumeRolePolicy, err := getEksAssumeRolePolicy()
if err != nil {
return nil, err
}
role, err := iam.NewRole(ctx, roleName, &iam.RoleArgs{
Name: roleName,
AssumeRolePolicy: assumeRolePolicy,
})
if err != nil {
return nil, err
}
_, err = iam.NewRolePolicyAttachment(ctx, "eks-cluster-policy", &iam.RolePolicyAttachmentArgs{
PolicyArn: "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy",
Role: role.Name(),
})
if err != nil {
return nil, err
}
_, err = iam.NewRolePolicyAttachment(ctx, "eks-service-policy", &iam.RolePolicyAttachmentArgs{
PolicyArn: "arn:aws:iam::aws:policy/AmazonEKSServicePolicy",
Role: role.Name(),
})
if err != nil {
return nil, err
}
return role, nil
}
func getEksAssumeRolePolicy() (interface{}, error) {
policy, _ := json.Marshal(map[string]interface{}{
"Version": "2012-10-17",
"Statement": []map[string]interface{}{
{
"Effect": "Allow",
"Principal": map[string]interface{}{
"Service": "eks.amazonaws.com",
},
"Action": "sts:AssumeRole",
},
},
})
return string(policy), nil
}
func createEksWorkerRole(ctx *pulumi.Context, roleName string) (*iam.Role, error) {
assumeRolePolicy, err := getEc2AssumeRolePolicy()
if err != nil {
return nil, err
}
role, err := iam.NewRole(ctx, roleName, &iam.RoleArgs{
Name: roleName,
AssumeRolePolicy: assumeRolePolicy,
})
if err != nil {
return nil, err
}
_, err = iam.NewRolePolicyAttachment(ctx, "eks-worker-policy", &iam.RolePolicyAttachmentArgs{
PolicyArn: "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy",
Role: role.Name(),
})
if err != nil {
return nil, err
}
_, err = iam.NewRolePolicyAttachment(ctx, "eks-cni-policy", &iam.RolePolicyAttachmentArgs{
PolicyArn: "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy",
Role: role.Name(),
})
if err != nil {
return nil, err
}
_, err = iam.NewRolePolicyAttachment(ctx, "ecr-policy", &iam.RolePolicyAttachmentArgs{
PolicyArn: "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly",
Role: role.Name(),
})
if err != nil {
return nil, err
}
return role, nil
}
func getEc2AssumeRolePolicy() (interface{}, error) {
policy, _ := json.Marshal(map[string]interface{}{
"Version": "2012-10-17",
"Statement": []map[string]interface{}{
{
"Effect": "Allow",
"Principal": map[string]interface{}{
"Service": "ec2.amazonaws.com",
},
"Action": "sts:AssumeRole",
},
},
})
return string(policy), nil
}
network.go
VPCやサブネットをnetwork.go
で作成します。
AZは事前に設定するリージョンから検出されます。
サブネット情報などをまとめて返り値としています。
package main
import (
"fmt"
"github.com/pulumi/pulumi-aws/sdk/go/aws"
"github.com/pulumi/pulumi-aws/sdk/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/go/pulumi"
)
type Network struct {
AvailabilityZones interface{}
VpcId interface{}
SubnetIds interface{}
}
func createNetwork(ctx *pulumi.Context) (*Network, error){
azs, err := aws.LookupAvailabilityZones(ctx, &aws.GetAvailabilityZonesArgs{
State: "available",
})
if err != nil {
return nil, err
}
vpc, err := ec2.NewVpc(ctx, "eks-vpc", &ec2.VpcArgs{
CidrBlock: "10.0.0.0/16",
})
if err != nil {
return nil, err
}
ig, err := ec2.NewInternetGateway(ctx, "eks-ig", &ec2.InternetGatewayArgs{
VpcId: vpc.ID(),
})
if err != nil {
return nil, err
}
rt, err := ec2.NewRouteTable(ctx, "eks-rt", &ec2.RouteTableArgs{
VpcId: vpc.ID(),
Routes: []map[string]interface{}{{
"cidrBlock": "0.0.0.0/0",
"gatewayId": ig.ID(),
},},
})
if err != nil {
return nil, err
}
var subnetIds []pulumi.IDOutput
usePrivateSubnets := false
for i, az := range azs.Names.([]interface{}) {
subnetName := fmt.Sprintf("eks-sn-%d", i)
cidrBlock := fmt.Sprintf("10.0.%d.0/24", i)
subnet, err := ec2.NewSubnet(ctx, subnetName, &ec2.SubnetArgs{
VpcId: vpc.ID(),
CidrBlock: cidrBlock,
AvailabilityZone: az,
MapPublicIpOnLaunch: !usePrivateSubnets,
})
if err != nil {
return nil, err
}
rtaName := fmt.Sprintf("eks-rta-%d", i)
_, err = ec2.NewRouteTableAssociation(ctx, rtaName, &ec2.RouteTableAssociationArgs{
SubnetId: subnet.ID(),
RouteTableId: rt.ID(),
})
if err != nil {
return nil, err
}
subnetIds = append(subnetIds, subnet.ID())
}
network := new(Network)
network.AvailabilityZones = azs
network.VpcId = vpc.ID()
network.SubnetIds = subnetIds
return network, nil
}
eks.go
eks.go
でEKSクラスタとEKSワーカーを作成します。
その前段としてNetworkとIAMの関数を呼び出しています。
今回はManaged Node Groupsでワーカーを立てるので事前にSecurity Groupを指定する必要がありません。
package main
import (
"github.com/pulumi/pulumi-aws/sdk/go/aws/eks"
"github.com/pulumi/pulumi/sdk/go/pulumi"
)
func createEks(ctx *pulumi.Context) error {
network, err := createNetwork(ctx)
if err != nil {
return err
}
eksClusterRole, err := createEksClusterRole(ctx, "eks-cluster-role")
if err != nil {
return err
}
eksWorkerRole, err := createEksWorkerRole(ctx, "eks-worker-role")
if err != nil {
return err
}
eksClusterName := "eks-cluster"
eksCluster, err := eks.NewCluster(ctx, eksClusterName, &eks.ClusterArgs{
Name: eksClusterName,
RoleArn: eksClusterRole.Arn(),
VpcConfig: map[string]interface{}{
"subnetIds": network.SubnetIds,
},
})
if err != nil {
return err
}
eksWorkerName := "eks-worker"
_, err = eks.NewNodeGroup(ctx, eksWorkerName, &eks.NodeGroupArgs{
ClusterName: eksCluster.Name(),
NodeGroupName: eksWorkerName,
NodeRoleArn: eksWorkerRole.Arn(),
ScalingConfig: map[string]int{
"DesiredSize": 2,
"MaxSize": 3,
"MinSize": 1,
},
SubnetIds: network.SubnetIds,
})
if err != nil {
return err
}
return nil
}
実行
作成したプラグラムを実行します。
1. スタックの作成
$ pulumi stack init pulumi-eks
2. Pulumi AWSプラグインのインストール
$ pulumi plugin install resource aws 1.17.0
3. AWSリージョンの設定
$ pulumi config set aws:region ap-northeast-1
4. golangプログラムのコンパイル
$ go get .
$ go install .
5. デプロイ
$ pulumi up
Updating (eks):
Type Name Status
+ pulumi:pulumi:Stack pulumi-eks-eks created
+ ├─ aws:ec2:Vpc eks-vpc created
+ ├─ aws:iam:Role eks-cluster-role created
+ ├─ aws:iam:Role eks-worker-role created
+ ├─ aws:iam:RolePolicyAttachment eks-cluster-policy created
+ ├─ aws:iam:RolePolicyAttachment eks-service-policy created
+ ├─ aws:iam:RolePolicyAttachment eks-worker-policy created
+ ├─ aws:iam:RolePolicyAttachment eks-cni-policy created
+ ├─ aws:iam:RolePolicyAttachment ecr-policy created
+ ├─ aws:ec2:Subnet eks-sn-1 created
+ ├─ aws:ec2:Subnet eks-sn-2 created
+ ├─ aws:ec2:InternetGateway eks-ig created
+ ├─ aws:ec2:Subnet eks-sn-0 created
+ ├─ aws:ec2:RouteTable eks-rt created
+ ├─ aws:eks:Cluster eks-cluster created
+ ├─ aws:ec2:RouteTableAssociation eks-rta-1 created
+ ├─ aws:ec2:RouteTableAssociation eks-rta-0 created
+ ├─ aws:ec2:RouteTableAssociation eks-rta-2 created
+ └─ aws:eks:NodeGroup eks-worker created
Resources:
+ 19 created
6. 確認
コマンドラインでも確認できます。
$ aws eks update-kubeconfig --name eks-cluster
$ kubectl get node
kubectl get node
NAME STATUS ROLES AGE VERSION
ip-10-0-0-35.ap-northeast-1.compute.internal Ready <none> 19m v1.14.7-eks-1861c5
ip-10-0-2-93.ap-northeast-1.compute.internal Ready <none> 19m v1.14.7-eks-1861c5
おわりに
golangを記述言語としてPulumiを使用してEKSクラスタが構築できることを確認しました。
golangを使用した場合のドキュメントがもっと増えることを期待します。