inductor's blog

nothing but self note :)

EKSでDockerから卒業すべくBottlerocketのマネージド型ノードグループを使ってみた

はじめに

この記事ではAWSの公式ブログ「Amazon EKS adds native support for Bottlerocket in Managed Node Groups」で取り上げられている内容を、eksctlを使わずCloudFormationでimmutableに実現するための方法を解説します。

aws.amazon.com

Bottlerocketとは

Bottlerocketは、AWSが開発しているコンテナ実行専用OSです。Fedora/RHELのCoreOSやVMwareのPhoton OS、Rancher OSなどと似ていて、コンテナを実行するためのランタイム以外余計なパッケージが入らない軽量なOSとなっています。

aws.amazon.com

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 and kubelet.

2. Amazon Linux 2からの開放

Amazon Linux 2はRHEL 7ベースでなんだかんだリリースから4年が経過しており、コンテナを動かすためには関係のない依存関係も含まれています。一方でBottlerocketはコンテナを動かすために作られているので余計なパッケージの依存関係がなく、軽量な他にAttack surfaceが抑えられるメリットもあります。AMI更新の頻度はAL2と比べても相対的に少なくなるでしょうし(これは事実ではなく推測)運用におけるメリットは大きいでしょう。

セキュリティに関するガイドはこちらにもあります。

github.com

Bottlerocket導入における考慮点

MNGを使うと従来のSelf hostedに比べて設定を大幅に簡素化できるメリットがあります。また、クラスター固有の情報に関しても省略できるパターンがあるため、CFnテンプレートの使い回しが非常に簡単というメリットもあります。ただし、Bottlerocketについては公式ブログに以下の記述があり注意が必要です。

aws.amazon.com

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を頑張ってデコードするとエンドポイントや証明書の情報が取れますが、既に破壊済みのクラスターなので何もできないです。

こんな調子で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'

上手く動くとこんなかんじ。

便利ですね!これでうまいことGood bye Docker from EKSしつつ運用コストも抑えられそうです。

んだばこのへんで。