はじめに
この記事ではAWSの公式ブログ「Amazon EKS adds native support for Bottlerocket in Managed Node Groups」で取り上げられている内容を、eksctlを使わずCloudFormationでimmutableに実現するための方法を解説します。
Bottlerocketとは
Bottlerocketは、AWSが開発しているコンテナ実行専用OSです。Fedora/RHELのCoreOSやVMwareのPhoton OS、Rancher OSなどと似ていて、コンテナを実行するためのランタイム以外余計なパッケージが入らない軽量なOSとなっています。
Bottlerocketの開発状況についてはBottlerocket Roadmap · GitHubを合わせてみると良いです。
マネージド型ノードグループ(MNG)とは
EKSでは比較的後発の機能で、以前まではクラスターのワーカーノードはEC2のASGを自分で作成してクラスターに参加させていたのを、AWSのインテグレーションで一発でできるようにしたリソースです。これを使うことでクラスターに参加させる複数ノードのライフサイクルを1つのリソースで管理できて便利です。GKEでは当初からノードプールと言う概念で使えていたので、それに追従する格好になっているとも言えます。
最初はMNGでできることも決して多くはなかったですが、Private subnet対応、Spot対応などを経てどんどん便利になってきました。
これまでのMNGはAmazon Linux 2ベースで、amiを指定する方法もあるにはあったんですが自分でいろいろ組み込まないといけなくて面倒でした。今回Bottlerocketに対応したことで以下のような恩恵が得られるようになります。
1. Dockerからの卒業
DockerランタイムがKubernetesでdeprecatedになったのは1.20からです。1.23にはdockershimが完全に削除される予定で、現在EKSは1.21なので緩やかにその波はやってきます。
Bottlerocketはコンテナランタイムにcontainerdを採用しているため、悩みがなくなるだけでなくリソース使用量がフットプリントの問題も若干解消されるでしょう。Amazon EKS optimized Bottlerocket AMIsのドキュメントでも以下のように記述があります。
The AMI is configured to work with Amazon EKS and it includes
containerd
andkubelet
.
2. Amazon Linux 2からの開放
Amazon Linux 2はRHEL 7ベースでなんだかんだリリースから4年が経過しており、コンテナを動かすためには関係のない依存関係も含まれています。一方でBottlerocketはコンテナを動かすために作られているので余計なパッケージの依存関係がなく、軽量な他にAttack surfaceが抑えられるメリットもあります。AMI更新の頻度はAL2と比べても相対的に少なくなるでしょうし(これは事実ではなく推測)運用におけるメリットは大きいでしょう。
セキュリティに関するガイドはこちらにもあります。
Bottlerocket導入における考慮点
MNGを使うと従来のSelf hostedに比べて設定を大幅に簡素化できるメリットがあります。また、クラスター固有の情報に関しても省略できるパターンがあるため、CFnテンプレートの使い回しが非常に簡単というメリットもあります。ただし、Bottlerocketについては公式ブログに以下の記述があり注意が必要です。
You can use launch templates with Bottlerocket for scenarios such as additional arguments to kubelet, or to fully control volume type and size. When using a launch template with a Bottlerocket managed node group, user data must be in TOML format. Refer to the Bottlerocket documentation for a complete list of Kubernetes settings allowed via user data.
EBSの暗号化設定やボリュームサイズ、その他メタデータを動的に書き換えたいなどのユースケースでは、Launch templateを定義してMNGリソースにアタッチする必要があります。このとき、Bottlerocketの制約上、User dataの部分にクラスターエンドポイントや証明書をアタッチする必要があり、これはクラスター固有の値なのでIaCの取り回しがききづらく厄介です(厳密CFnにおいてはCluster作成時のOutputをimportvalueすれば良いと思いますが、Terraformを使う場合など考慮が別で必要です)。
具体的には、AL2ベースとBottlerocketで以下のような差分が発生します。ちなみにこのUserDataを頑張ってデコードするとエンドポイントや証明書の情報が取れますが、既に破壊済みのクラスターなので何もできないです。
眺めてる pic.twitter.com/IJ3s8OnHEf
— inductor (@_inductor_) October 30, 2021
こんな調子でUser dataがゴリゴリの依存関係になってしまうため、Launch templateの取り扱いについては注意が必要です。ただし、MNGなのでそもそもLaunch templateを上書きする必要がないのであれば、そもそもLaunch templateごと取っ払ってしまおうということで、以下のようなYAMLを作ってみました。
AWSTemplateFormatVersion: 2010-09-09 Description: 'EKS Managed Bottlerocket Nodes' Metadata: "AWS::CloudFormation::Interface": ParameterGroups: - Label: default: "Project Name Prefix" Parameters: - PJPrefix Parameters: PJPrefix: Type: String ClusterName: Type: String NodeDesiredSize: Type: String NodeMaxSize: Type: String NodeMinSize: Type: String Mappings: ServicePrincipalPartitionMap: aws: EC2: ec2.amazonaws.com EKS: eks.amazonaws.com EKSFargatePods: eks-fargate-pods.amazonaws.com aws-cn: EC2: ec2.amazonaws.com.cn EKS: eks.amazonaws.com EKSFargatePods: eks-fargate-pods.amazonaws.com aws-us-gov: EC2: ec2.amazonaws.com EKS: eks.amazonaws.com EKSFargatePods: eks-fargate-pods.amazonaws.com Resources: ManagedNodeGroup: Type: 'AWS::EKS::Nodegroup' Properties: AmiType: BOTTLEROCKET_x86_64 ClusterName: !Ref ClusterName InstanceTypes: - m5.xlarge - m4.xlarge - m3.xlarge - t3.xlarge - t2.xlarge CapacityType: SPOT Labels: alpha.eksctl.io/cluster-name: !Ref ClusterName alpha.eksctl.io/nodegroup-name: managed-ng-private-bottlerocket-spot role: worker NodeRole: !GetAtt - NodeInstanceRole - Arn NodegroupName: managed-ng-private-bottlerocket-spot ScalingConfig: DesiredSize: !Ref NodeDesiredSize MaxSize: !Ref NodeMaxSize MinSize: !Ref NodeMinSize Subnets: - { "Fn::ImportValue": !Sub "${PJPrefix}-private-subnet-a" } - { "Fn::ImportValue": !Sub "${PJPrefix}-private-subnet-c" } Tags: alpha.eksctl.io/nodegroup-name: managed-ng-private-bottlerocket-spot alpha.eksctl.io/nodegroup-type: managed nodegroup-type: Bottlerocket NodeInstanceRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Statement: - Action: - 'sts:AssumeRole' Effect: Allow Principal: Service: - !FindInMap - ServicePrincipalPartitionMap - !Ref 'AWS::Partition' - EC2 Version: 2012-10-17 ManagedPolicyArns: - !Sub >- arn:${AWS::Partition}:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonEKSWorkerNodePolicy' - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonEKS_CNI_Policy' Path: / Tags: - Key: Name Value: !Sub '${AWS::StackName}/NodeInstanceRole'
上手く動くとこんなかんじ。
— inductor (@_inductor_) November 2, 2021
便利ですね!これでうまいことGood bye Docker from EKSしつつ運用コストも抑えられそうです。
んだばこのへんで。