Open15

k8sに入門していく

現状のレベル感

「イラストでわかるdockerとkubernetes」を一冊読み終えて、「Kubernetes完全ガイド」を6.3まで読み終えたところ。所属企業のプロダクトのリリースフローやインフラの概要を理解したところで、業務でこれからkubernetesを触っていく予定。

なぜスクラップを書こうと思ったか

今まで読書したことをTwitterで呟いていたのですが、
(1) 140字という字数制限があって学んだことをアウトプットしにくいし、
(2) アウトプットをきれいにフォーマットして整理することはできないし、
(3) 長文を連投していくとフォロワーから鬱陶しく思われそう(特に興味のない内容であるほど)
ということがあるので、学んだことをアウトプットしていく他の場所を探していました。

最初はNotionを使ってまとめていくこともやってみたのですが、やはり他のエンジニアの目に触れやすい場所に文章を置いて、あわよくば交流のきっかけになったり、あるいは、書いたもの自体が資産化して欲しいという願望(欲望?)もあったので、もっと他に良い場所はないかなと思っていました。

ちょうど今日、Zennのスクラップ機能の存在を知ったので、ここに書いてみることにしました。気に入ったら学びながらここにアウトプットを継続していくつもりです!

Kubernetes完全ガイド

6.4 ExternalIP Service

特定のNodeのIPアドレス:Portで受信したトラフィックをコンテナに転送するService。

Point

  • 基本的にはExternalIP ServiceではなくNodePort Serviceを使う!
  • ExternalIPは全てのNodeのIPを記述する必要はない

cdkでEKSクラスターを遊ぼうと思った時につまづいた時のメモ

問題:NodeGroupを作成できたが、個々のNodeが表示されなかった
解決策:IAMユーザーにsystem:masters アクセス許可を付与する(参考

user := iam.User_FromUserName(stack, jsii.String("ImportedUserByName"), jsii.String("user_name"))
cluster.AwsAuth().AddUserMapping(user, &eks.AwsAuthMapping{
    Groups: &[]*string{jsii.String("system:masters"),},
})

問題:ローカルからkubectlでEKSクラスターを操作できなかった。
解決策:AWS CLI利用時にスイッチロールしていたので、このロールにもsystem:masters アクセス許可を付与する

mfaRole := iam.Role_FromRoleName(stack, jsii.String("ImportedRoleByName"), jsii.String("AdminRole"))
cluster.AwsAuth().AddRoleMapping(mfaRole, &eks.AwsAuthMapping{
    Groups: &[]*string{jsii.String("system:masters"),},
})

EKSクラスターからNodeGroupを直接作成した場合

var cluster eks.Cluster 
cluster.AddNodegroupCapacity(jsii.String("EKSNodeGroupCapacity"), &eks.NodegroupOptions{
    // 省略
}

問題点

  • NodeGroupに対応したAutoScalingGroupをCDKからカスタマイズすることができなくなる。
    (NodeGroupに対応したAutoScalingGroupが作成されていることはAWSコンソールから確認できる。しかし、CDKの公式ドキュメントを読む限り、CDKではNodeGroupからAutoScalingGroupを参照する術が用意されていない。)

備忘メモ

cluster.AddNodegroupCapacity(jsii.String("EKSNodeGroupCapacity"), &eks.NodegroupOptions{
    AmiType: eks.NodegroupAmiType_AL2_X86_64,
    CapacityType: eks.CapacityType_SPOT,
    DesiredSize: jsii.Number(3),
    DiskSize: jsii.Number(10),
    InstanceTypes: &[]ec2.InstanceType{
        ec2.NewInstanceType(jsii.String("t2.micro")),
        ec2.NewInstanceType(jsii.String("t2.small")),
	ec2.NewInstanceType(jsii.String("t2.medium")),
	ec2.NewInstanceType(jsii.String("t3.micro")),
	ec2.NewInstanceType(jsii.String("t3.small")),
	ec2.NewInstanceType(jsii.String("t3.medium")),
    },
    Labels: &map[string]*string {
        "app": jsii.String("practice"),
    },
    MaxSize: jsii.Number(6),
    MinSize: jsii.Number(3),
    NodegroupName: jsii.String("eks-node-group"),
    NodeRole: nodeRole,
    Subnets: &ec2.SubnetSelection{
	SubnetType: ec2.SubnetType_PRIVATE_WITH_NAT,
    },
    Tags: &map[string]*string {
        "Service": jsii.String("service_name"),
	"Environment": jsii.String("production"),
    },
})

あらかじめ起動テンプレートを作成しておき、EKSクラスターからNodeGroupを作成する際に起動テンプレートを指定することでNodeに適用するRoleとSecurityGroupとを渡すことができそうと考えて実験してみた。

sg := ec2.NewSecurityGroup(stack, jsii.String("EKSNodeSecurityGroup"), &ec2.SecurityGroupProps{
    // 省略
})

nodeRole := iam.NewRole(stack, jsii.String("EKSNoeRole"), &iam.RoleProps{
    // 省略
})

launchTemplate := ec2.NewLaunchTemplate(stack, jsii.String("EksNodesLaunchTemplate"), &ec2.LaunchTemplateProps{
    Role: role,
    SecurityGroup: sg,
    // 省略
})

cluster.AddNodegroupCapacity(jsii.String("EKSNodeGroupCapacity"), &eks.NodegroupOptions{
    LaunchTemplateSpec: &eks.LaunchTemplateSpec{
        Id: launchTemplate.LaunchTemplateId(),
        Version: launchTemplate.LatestVersionNumber(),
    }
    // 省略
    NodeRole: nodeRole,
})

上記の場合はcdk deploy時にスタックの作成に失敗し、次のエラーが返される。

Launch template eks-nodes-launch-template should not specify an instance profile. The noderole in your request will be used to construct an instance profile.

NodeGroupの方でRoleを指定しているから起動テンプレートでは指定しないというわけね!

こちらで指定したSecurityGroupをNodeGroupに適用することは出来なさそうと判明した。

以下のようこちらで指定したSecurityGroupを起動テンプレートに渡すとUnhealthy nodes in the kubernetes clusterというメッセージが表示されて、NodeCreationFailureが発生する。※渡すSecurityGroupで全てのインバウンドトラフィックを許可しても同様。

sg := ec2.NewSecurityGroup(stack, jsii.String("EKSNodeSecurityGroup"), &ec2.SecurityGroupProps{
    // 省略
})

launchTemplate := ec2.NewLaunchTemplate(stack, jsii.String("EksNodesLaunchTemplate"), &ec2.LaunchTemplateProps{
    SecurityGroup: sg,
    // 省略
})

nodeRole := iam.NewRole(stack, jsii.String("EKSNoeRole"), &iam.RoleProps{
    // 省略
})

cluster.AddNodegroupCapacity(jsii.String("EKSNodeGroupCapacity"), &eks.NodegroupOptions{
    LaunchTemplateSpec: &eks.LaunchTemplateSpec{
        Id: launchTemplate.LaunchTemplateId(),
        Version: launchTemplate.LatestVersionNumber(),
    }
    // 省略
    NodeRole: nodeRole,
})

次のエラーが発生する場合は

Instance market options are not supported with the launch template.

launchTemplateからInstanceTypeとMachineImageを削除して、NodeGroupの方でInstanceTypeとAmiTypeとを指定するように変更すると解決できる。

cluster.AddNodegroupCapacity(jsii.String("EKSNodeGroupCapacity"), &eks.NodegroupOptions{
    AmiType: eks.NodegroupAmiType_AL2_X86_64,
    InstanceTypes: &[]ec2.InstanceType{
        ec2.NewInstanceType(jsii.String("t2.micro")),
        ec2.NewInstanceType(jsii.String("t2.small")),
    }
}

EKSクラスターからAutoScalingGroupを直接作成した場合

var cluster eks.Cluster
cluster.AddAutoScalingGroupCapacity(jsii.String("EKSAutoScalingGroup"), &eks.AutoScalingGroupCapacityOptions{
    // 省略
})

問題点

  • AWSコンソールのEKSクラスターに対応するノードグループを確認すると、「ノードグループなし」と表示される。
  • AutoScalingGroupに付与されるRoleとSecurityGroupがAWS側で自動作成されてしまい、これらをカスタマイズする方法がなさそう。

備忘メモ

cluster.AddAutoScalingGroupCapacity(jsii.String(""), &eks.AutoScalingGroupCapacityOptions{
    AllowAllOutbound: jsii.Bool(true),
    AssociatePublicIpAddress: jsii.Bool(false),
    AutoScalingGroupName: jsii.String("eks-auto-scaling-group"),
    DesiredCapacity: jsii.Number(3),
    MaxCapacity: jsii.Number(6),
    MinCapacity: jsii.Number(1),
    NewInstancesProtectedFromScaleIn: jsii.Bool(false),
    TerminationPolicies: &[]autoscaling.TerminationPolicy{
        autoscaling.TerminationPolicy_ALLOCATION_STRATEGY,
	autoscaling.TerminationPolicy_OLDEST_LAUNCH_TEMPLATE,
	autoscaling.TerminationPolicy_OLDEST_INSTANCE,
    },
    UpdatePolicy: autoscaling.UpdatePolicy_RollingUpdate(&autoscaling.RollingUpdateOptions{
	MaxBatchSize: jsii.Number(1),
	MinInstancesInService: jsii.Number(1),
	WaitOnResourceSignals: jsii.Bool(true),
	PauseTime: cdk.Duration_Seconds(jsii.Number(5)),
    }),
    VpcSubnets: &ec2.SubnetSelection{
	SubnetType: ec2.SubnetType_PRIVATE_WITH_NAT,
    },
    InstanceType: ec2.NewInstanceType(jsii.String("t2.micro")),
        MachineImageType: eks.MachineImageType_AMAZON_LINUX_2,
})

作成したAutoScalingGroupをEKSクラスターと関連づける場合

nodeRole := iam.NewRole(stack, jsii.String("EKSNodeRole"), &iam.RoleProps{
  // 省略
})

sg := ec2.NewSecurityGroup(stack, jsii.String("EKSNodeSecurityGroup"), &ec2.SecurityGroupProps{
    // 省略
})

asg := autoscaling.NewAutoScalingGroup(stack, jsii.String("EKSAutoScalingGroup"), &autoscaling.AutoScalingGroupProps{
    // 省略
    SecurityGroup: sg,
    Role: nodeRole
})

cluster.ConnectAutoScalingGroupCapacity(asg, &eks.AutoScalingGroupOptions{
    // 省略
})

嬉しい点

  • ユーザーが指定したSecurityGroupをAutoScalingGroupに付与することができる。

問題点

  • AWSコンソールのEKSクラスターの画面からノードグループを確認することができない。
  • 作成したAutoScalingGroupにカスタマイズされた起動テンプレートが関連付けられている場合、そのAutoScalingGroupをEKSクラスターに関連づけることができない。

備忘メモ

nodeRole := iam.NewRole(stack, jsii.String("EKSNodeRole"), &iam.RoleProps{
    AssumedBy: iam.NewServicePrincipal(jsii.String("ec2.amazonaws.com"), &iam.ServicePrincipalOpts{}),
    Path: jsii.String("/"),
    RoleName: jsii.String("eks-node-role"),
    ManagedPolicies: &[]iam.IManagedPolicy{
        iam.ManagedPolicy_FromAwsManagedPolicyName(jsii.String("AmazonEKSWorkerNodePolicy")),
        iam.ManagedPolicy_FromAwsManagedPolicyName(jsii.String("AmazonEKS_CNI_Policy")),
        iam.ManagedPolicy_FromAwsManagedPolicyName(jsii.String("AmazonEC2ContainerRegistryReadOnly")), 
    },
})

sg := ec2.NewSecurityGroup(stack, jsii.String("EKSNodeSecurityGroup"), &ec2.SecurityGroupProps{
    Vpc: vpc,
    AllowAllOutbound: jsii.Bool(true),
    Description: jsii.String("security group for eks cluster nodes"),
    SecurityGroupName: jsii.String("eks-node-security-group"),
})

asg := autoscaling.NewAutoScalingGroup(stack, jsii.String("EKSAutoScalingGroup"), &autoscaling.AutoScalingGroupProps{
    AutoScalingGroupName: jsii.String("eks-auto-scaling-group"),
    DesiredCapacity: jsii.Number(3),
    MaxCapacity: jsii.Number(6),
    MinCapacity: jsii.Number(1),
    NewInstancesProtectedFromScaleIn: jsii.Bool(false),
    TerminationPolicies: &[]autoscaling.TerminationPolicy{
	 autoscaling.TerminationPolicy_ALLOCATION_STRATEGY,
	 autoscaling.TerminationPolicy_OLDEST_LAUNCH_TEMPLATE,
	 autoscaling.TerminationPolicy_OLDEST_INSTANCE,
    },
    UpdatePolicy: autoscaling.UpdatePolicy_RollingUpdate(&autoscaling.RollingUpdateOptions{
	 MaxBatchSize: jsii.Number(1),
	 MinInstancesInService: jsii.Number(1),
	 WaitOnResourceSignals: jsii.Bool(true),
	 PauseTime: cdk.Duration_Seconds(jsii.Number(5)),
    }),
    Vpc: vpc,
    VpcSubnets: &ec2.SubnetSelection{
        SubnetType: ec2.SubnetType_PRIVATE_WITH_NAT,
    },
    Role: nodeRole,
    MachineImage: ec2.MachineImage_LatestAmazonLinux(&ec2.AmazonLinuxImageProps{
	CpuType: ec2.AmazonLinuxCpuType_X86_64,
	Generation: ec2.AmazonLinuxGeneration_AMAZON_LINUX_2,
    }),
    InstanceType: ec2.NewInstanceType(jsii.String("t2.micro")),
    SecurityGroup: sg,
})

cluster.ConnectAutoScalingGroupCapacity(asg, &eks.AutoScalingGroupOptions{
    BootstrapEnabled: jsii.Bool(true),
    MapRole: jsii.Bool(true),
})
  • AutoScalingGroupにSecurityGroupが2つ付与されるようになり、一方は上記のコードで記述したもので、もう一方はAWS側で自動作成されたものである。

上記の通り、NodeGroup作成時にNodeに適用するSecurityGroupをこちらで指定することは出来なさそうなので、今後は、NodeGroup作成後にAWS側で自動作成してNodeに適用しているSecurityGroupを検索してきて、それに対してインバウンドルールを追加することが出来ないか試してみる。

公式ドキュメントによれば、NodeへのSecurity Groupの適用はマニフェスト側で記述するっぽい。。。!!!

後日試してみる。

わかった!
以下のような感じでEKSクラスター作成後に、クラスターセキュリティグループに対してイングレスルールを追加することで、ALBからNodeGroupへのアクセスを許可できる模様。

// EKSクラスターの作成
cluster := eks.NewCluster(stack, jsii.String("EKSCluster"), &eks.ClusterProps{
	ClusterName: jsii.String("eks-cluster"),
	DefaultCapacity: jsii.Number(0), // デフォルトインスタンスは作らない
	EndpointAccess: eks.EndpointAccess_PUBLIC(),
	MastersRole: masterRole, // クラスターのマスターロール
	Version: eks.KubernetesVersion_V1_21(), // kubernetesのバージョン
	Vpc: vpc, // EKSクラスターをデプロイするVPC
})

// ALBからEKSクラスターへのIngress Accessを許可する
cluster.ClusterSecurityGroup().AddIngressRule(
	ec2.Peer_SecurityGroupId(sgAlb.SecurityGroupId(), jsii.String("")),
	ec2.Port_Tcp(jsii.Number(80)),
	jsii.String("Allow access from ALB"),
	jsii.Bool(true),
)

実はALBを作成するコードを書く必要はないので、EKSクラスターのセキュリティグループをカスタマイズする必要がなかったことが判明。。。

AWS LoadBalancer Controllerをインストールすることで、k8sのIngressリソース作成時にALBがプロビジョニングされるようになり、この時自動的にセキュリティグループやロールなども自動的に設定してくれた!

AWS LoadBalancer Controllerのインストール手順は公式ドキュメントの通りであるが、CDKの場合は以下のようにプロパティ一個の追加で実現ができる。

cluster := eks.NewCluster(stack, jsii.String("EKSCluster"), &eks.ClusterProps{
        AlbController: &eks.AlbControllerOptions{
		Version: eks.AlbControllerVersion_V2_4_1(),
	},
        // 省略
})

AWS LoadBalancer Controllerがインストールされた状態でDeployment, NodePort Service, Ingressリソースを作成すると、ALBが自動的に作成される。

deployment := map[string]interface{}{
	"apiVersion": jsii.String("apps/v1"),
	"kind": jsii.String("Deployment"),
	"metadata": map[string]*string{
		"name": jsii.String("front-deployment"),
	},
	"spec": map[string]interface{}{
		"replicas": jsii.Number(3),
		"selector": map[string]interface{}{
			"matchLabels": label,
		},
		"template": map[string]interface{}{
			"metadata": map[string]map[string]*string{
				"labels": label,
			},
			"spec": map[string]interface{}{
				"containers": []map[string]interface{}{
					{
						"name": jsii.String("app"),
						"image": repos.App.RepositoryUri(),
						"ports": []map[string]*float64{
							{"containerPort": jsii.Number(8080),},
						},
					},
					{
						"name": jsii.String("web"),
						"image": repos.Web.RepositoryUri(),
						"ports": []map[string]*float64{
							{"containerPort": jsii.Number(80),},
						},
					},
				},
			},
		},
	},
}

service := map[string]interface{}{
	"apiVersion": jsii.String("v1"),
	"kind": jsii.String("Service"),
	"metadata": map[string]*string{
		"name": jsii.String("front-nodeport-service"),
	},
	"spec": map[string]interface{}{
		"type": jsii.String("NodePort"),
		"ports": []map[string]interface{}{
			{
				"name": jsii.String("http-port"),
				"protocol": jsii.String("TCP"),
				"port": jsii.Number(8080),
				"targetPort": jsii.Number(80),
			},
		},
		"selector": label,
	},
}

ingress := map[string]interface{}{
	"apiVersion": jsii.String("networking.k8s.io/v1"),
	"kind": jsii.String("Ingress"),
	"metadata": map[string]interface{}{
		"name": jsii.String("front-ingress"),
		"annotations": map[string]interface{}{
			"kubernetes.io/ingress.class": jsii.String("alb"),
    		"alb.ingress.kubernetes.io/scheme": jsii.String("internet-facing"), //外部公開
			"alb.ingress.kubernetes.io/target-type": jsii.String("instance"),
		},
	},
	"spec": map[string]interface{}{
		"rules": []map[string]interface{}{
			{
				"http": map[string]interface{}{
					"paths": []map[string]interface{}{
						{
							"path": jsii.String("/"),
							"pathType": jsii.String("Prefix"),
							"backend": map[string]interface{}{
								"service": map[string]interface{}{
									"name": jsii.String("front-nodeport-service"),
									"port": map[string]interface{}{
										"number": jsii.Number(8080),
									},
								},
							},
						},
					},
				},
			},
		},
	},
}

// マニフェストの適用
eks.NewKubernetesManifest(stack, jsii.String("Manifest"), &eks.KubernetesManifestProps{
	Cluster: cluster,
	Manifest: &[]*map[string]interface{}{
		&deployment,
		&service,
		&ingress,
	},
})

あ、何を作ろうとしているのか記述していなかったので、作ろうとしている構成図を掲載しておく。

k8s練習用のインフラ構成

ログインするとコメントできます