※1 閉域:インターネット接続なし、と読み替えてください。英語だとoff-lineだそう(ググる時捗ります)
※2 kubernetes v1.13 を利用
ドコモではRedshiftを中核にした分析基盤の前段に、こんな形の前処理システムの運用をしています。
オンプレミスで構築された各システムからのデータをDirectConnect経由で受け取り、整形し、DWHへ格納する、よくあるNW構成のシステムです。
このようなシステムの常として、主にセキュリティの観点から、
- インターネット接続不可、もしくはプロキシ経由でのみ可能
という制約が付いて回ります( 閉域 と私たちは良く呼びます)。
このような制限下でのシステム開発、ダミーデータや検証環境をしっかり用意できれば良いのですが、なんだかんだ理想通りにはいかず……
- 検証環境下で開発、ビルド
- インターネット疎通経路のない本番環境へと転送、デプロイ
- エラーなどログの調査、改善点の洗い出し
といったサイクルを回しながら進めることになるものの、
- ネットワーク的に断絶されているがゆえ、1サイクルを回すのに時間かかる
- 本番環境内での作業が増え、検証環境との解離が起きがち
と、開発自体が複雑/高負荷になりがちでした。また当初は予定通りの動作であったとしても、その後の変更への対応の負担が大きく、対向システムの変化の速さ に追従しきれないことも…
そのため現在、このようなアーキテクチャの前処理システムの開発・運用を一部で進めています。
EKSを中核に1
- コンテナの可搬性を活かし、制限環境下でも迅速な開発を可能にする
- Kubernetesにより、水平スケーリングをシンプルに行えるようにする
のが狙いのアーキテクチャ、です。色々大変な想いもしましたが、上記のメリットはそれを補いあまりあるものを得られているな、と感じています。
せっかくのアドベントカレンダーの機会をいただけたので、この 閉域環境でのEKSの利用 について少し内部に踏み込みつつ、まとめていこうと思います。
模擬環境の構築
要点
- EKSのプライベートアクセスオプションをによりControlPlaneへの経路作成、VPC内からのアクセスを可能に。
- VPC Endpointを利用し経路確保。必要なのはECR、S3、EC2。
- AMI起動時に明示的に認証パラメータを注入し、ControlPlane側にノードとして認識させる。
勘所を押さえれば、全てAWSが提供する手段で完結するので、さした手間なしに作成できます。
Kubernetesの構成要素とEKSの職掌範囲
勘所を押さえるのに簡単に内部構造から。
[kubernetes blog](https://kubernetes.io/blog/2018/07/18/11-ways-not-to-get-hacked/)より引用
Kubernetesは上図のようにいくつかのコンポーネントから成り立っており、ユーザとのインターフェースや全体管理を行うMaster (Cotrol Plane、CPlaneとも)と、実際にアプリケーションコンテナが動作するNodeの部分へと大別されます。
EKSはこれらのうち、主にMasterの部分を提供するサービスです。2
[Running Kubernetes at Amazon scale using Amazon EKS](https://d1.awsstatic.com/events/reinvent/2019/REPEAT_1_Running_Kubernetes_at_Amazon_scale_using_Amazon_EKS_CON212-R1.pdf) re:Invent 2019セッション資料より引用
Nodeの部分はベースとなるAMIからEC2を起動し、Masterへと登録/認識させる、という割と下回りを意識する流れになります(CloudFormationが提供されているので操作自体は簡素ではあります)。
この操作を行う際、Node内の構成要素(kubelet, コンテナランタイム、ネットワーキング)について、全て動作可能な状態となるように必要なコンポーネントのダウンロードや認証等の通信が発生します。
これらについて、なにかしらの経路で通信できるようにするか、事前に入れ込んでおけば、閉域内でもクラスタ構築が可能になる、と言ったかたちとなります。
EKS-Optimized AMI
それではどのようにこれら要素を整えれば良いのか、NodeのもとになるAMI(EKS-Optimized AMI)について踏み込んでみましょう。AWSが配布しているAMIの、ビルドに使用されるスクリプトがGitHubで公開されています。
ビルドにはPackerが利用されています。利用したことがないと最初は??となるかもしれませんが、EC2上でシェルスクリプト走らせたり、設定ファイルを配置したりして後AMIを作り上げているものだと思っておけば大丈夫です。
-
eks-worker-al2.json
で各種パラメータ定義- amzn2-ami-minimal-hvm(最低限のライブラリのみが入ったAmazon Linux 2のAMI)をベースAMIとして指定
-
files
`下の設定ファイルを転送 -
install-worker.sh
起動
といった流れとなっています(実際に自分でAMIを焼きたければ、Makefile
があるのでmake
を使うのが簡便です)。
このうちinstall-worker.sh
に前述の必要なコンポーネントの導入が記述されています。
コンテナランタイム
こちらの通り、Dockerがランタイムとしてインストールされます。device-mapperやlvmもこちらでインストールされています。
kubelet
こちらで、AWSの用意したバイナリをS3から取得、設定がされるようになっています。aws s3 ls amazon-eks/1.14.7/2019-09-27/bin/linux/amd64/
を打つとバイナリの配置されているS3が見られます(バージョンや日付、OSなどは適宜読み替えてください)。このamazon-eks
バケットに、kubelet以外のEKS動作に必要なファイル類(IAMと連携するためのaws-iam-authenticator
など)が収められています。
ネットワーキング
ここまでは予めAMIの内部に収められるのですが、ネットワーキングについては少々異なります。
EKS(※v1.11以降)では
-
kube-proxy
: Podの通信先を制御。EKSだとデフォルトでNodeのiptablesを制御するモードで動作 -
coredns
: Service(Kubernetesの外部への公開点の概念)にDNSを付与 -
aws-nodes
: kubeletと連携してPodに対しVPC内でWorkするPrivateIPアドレスを付与
の3つがコンテナとして起動され、動作するのですが、これらに関してはAMIの中には収めらない状態です。
つまり起動した際、AWSのコンテナレジストリサービス(ECR)からPullしてくることで、NodeとしてWorkするような仕組みとなっています(EKSでKubernetes 1.13が公開される前は、この辺をユーザーがゴニョゴニョすることで無理矢理off-lineで動作させるしんどい状況だったりしました)。
これらについては、ECRとS3のVPCエンドポイントを付与することで、v1.13より解決が可能です。ポイントとしては
- ECRのVPCエンドポイントは2種類。dkr.ecr...(コンテナ本体のPull元)だけでなくapi.ecr...もつける(認証に必要なため)
- S3のエンドポイントも必要(ECRのストレージがS3のため)。最小の権限はこちら。
EKSからのNodeの認識
Nodeを起動した際、EKS側から認識させる必要があります。通常CloudFormationからNodeを起動すると、自動でEKS側の情報をEKSのAPIを利用し取得してくれるのですが、EKSそのものについてはPrivateLinkが現在存在しないため、そのままでは認識がされません。
そのため、AMIの起動時にパラメータとして予め渡してあげることで、EKSへのAPIコールを回避しNodeとして認識させることが可能です。
先ほど触れたAMI内部に転送されているファイルのなかにある、bootstrap.shが起動時に実行されるスクリプトになります。これに
- EKSのKubernetesのMasterとしてのエンドポイント(ややこしいですが...
kube-apiserver
へのエンドポイントと読み替え他方がわかりやすいかもしれません。) - その認証情報
の2点を、CloudFormationから渡すと、Nodeとして認識がされます。
EKSのPrivateAccess
このオプションをEnableにすると、Masterエンドポイントに対するPrivate IPが払い出され、EKSが対象としたVPC内部からはDNSでの名前解決も可能となります。
これをEnableの状態で、PublicAccessの方をDisableにすると、対象のVPC内からのみkubectlを用いてKubernetesの操作が可能な状態となります(※ 正確には、Enabling DNS resolution for Amazon EKS cluster endpoints | AWS Compute Blog工夫すればピアリングした隣のVPCからもエンドポイントへの名前解決が可能です)。
EKSとkubectlとIAMの関係
EKSでは、kubectlが打たれた際、クライアントのIAMを認証として利用します。
Masterを作成した時点では、その作成者がAdminとされるため、閉域内からkubectlを叩いて利用するためには作成者のIAMキーを渡すか、作成後に他のIAMユーザーかロールをAdminとして追加する必要がでます。
環境構築
長くなってしまいましたが、それでは閉域を模擬した環境で、EKSを使用してみます。
(CloudFormationや構築スクリプトを付しておきますが、参考程度に。)
1 NW+関連サービス
Direct Connectを用意はできないので、まずVPC Peeringを越えてインターネットへは出ていけないことを利用して閉域を模擬した環境を作ります。(今だったらSSM Session Managerを利用するのが良いかもしれません)
EKS自身が2以上のサブネットを必要とするのでそれについても作成(今回利用はしません)。
また、EKS自身が利用するためのIAMロールや、kubectlを打つためにEC2へ付与するロールも、ここで作成しています。
(VPCFlowLogsつけたり色々してますが無視してください、開発環境でもなるべくつけるようにしようという弊社ポリシーです)
`1-infrastructure.yaml`
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
EnvName:
Type: String
Default: yoda-close-nw
Description: Component application identifier used by this sequential deployment
AZ1:
Type: String
Default: ap-northeast-1a
Description: Main AZ
AZ2:
Type: String
Default: ap-northeast-1c
Description: Second AZ (Just for EKS K8s endpoints)
BastionVPCCIDR:
Type: String
Default: 172.24.0.0/24
Description: Bastion VPC CIDR block
BastionPublicSubnetCIDR:
Type: String
Default: 172.24.0.0/25
Description: Public Subnet in Bastion VPC CIDR block (For bastion, natgw)
K8sVPCCIDR:
Type: String
Default: 172.25.0.0/16
Description: K8s VPC CIDR block
K8sWorkerSubnet1CIDR:
Type: String
Default: 172.25.0.0/17
Description: K8s Worker Subnet in AZ1 CIDR block
K8sWorkerSubnet2CIDR:
Type: String
Default: 172.25.128.0/18
Description: K8s Worker Subnet in AZ2 CIDR block
K8sOperatorSubnetCIDR:
Type: String
Default: 172.25.192.0/18
Description: kubectl instance located subnet CIDER
YourComputerIPAddress:
Type: String
Default: 121.118.77.48
Description: Access point IP address
InstanceType:
Description: Server EC2 instance type
Type: String
Default: t3.nano
ConstraintDescription: must be a valid EC2 instance type.
RootVolumeSize:
Default: 8
Type: String
RootVolumePath:
Description: Amazon Linux=>/dev/xvda, Ubuntu=>/dev/sda1
Default: /dev/xvda
Type: String
KeyName:
Description: Name of an existing EC2 KeyPair to enable SSH access to the instance
Type: AWS::EC2::KeyPair::KeyName
ConstraintDescription: must be the name of an existing EC2 KeyPair.
AmiId:
Description: kubectl installed ami
Type: String
Resources:
################################################################################
### VPC ########################################################################
################################################################################
BastionVPC:
Type: 'AWS::EC2::VPC'
Properties:
CidrBlock: !Sub ${BastionVPCCIDR}
EnableDnsSupport: 'true'
EnableDnsHostnames: 'true'
Tags:
- Key: Name
Value: !Sub ${EnvName}-bastion-vpc
K8sVPC:
Type: 'AWS::EC2::VPC'
Properties:
CidrBlock: !Sub ${K8sVPCCIDR}
EnableDnsSupport: 'true'
EnableDnsHostnames: 'true'
Tags:
- Key: Name
Value: !Sub ${EnvName}-k8s-vpc
### VPC Peering ###
VPCPeering:
Type: AWS::EC2::VPCPeeringConnection
Properties:
PeerVpcId: !Ref BastionVPC # Accepter
Tags:
- Key: Name
Value: !Sub ${EnvName}-vpcpeering
VpcId: !Ref K8sVPC # Requester
################################################################################
### Subnet #####################################################################
################################################################################
BastionPublicSubnet:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AZ1
CidrBlock: !Ref BastionPublicSubnetCIDR
VpcId: !Ref BastionVPC
Tags:
- Key: Name
Value: !Sub ${EnvName}-bastion-public-subnet
K8sOperatorSubnet:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AZ1
CidrBlock: !Sub ${K8sOperatorSubnetCIDR}
VpcId: !Ref K8sVPC
Tags:
- Key: Name
Value: !Sub ${EnvName}-k8s-operator-subnet
K8sWorkerSubnet1:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AZ1
CidrBlock: !Sub ${K8sWorkerSubnet1CIDR}
VpcId: !Ref K8sVPC
Tags:
- Key: Name
Value: !Sub ${EnvName}-k8s-worker-subnet-1
K8sWorkerSubnet2:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AZ2
CidrBlock: !Sub ${K8sWorkerSubnet2CIDR}
VpcId: !Ref K8sVPC
Tags:
- Key: Name
Value: !Sub ${EnvName}-k8s-worker-subnet-2
################################################################################
### Internet Gateway ###########################################################
################################################################################
BastionInternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${EnvName}
AttachBastionInternetGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref BastionVPC
InternetGatewayId:
Ref: BastionInternetGateway
################################################################################
### Route Table & Rule #########################################################
################################################################################
# Route Table
BastionPublicSubnetRouteTable: # RTB for ops public subet
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref BastionVPC
Tags:
- Key: Name
Value: !Sub ${EnvName}-bastion-public-subnet-rtb
K8sWorkerRouteTable: # For master & worker subet
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref K8sVPC
Tags:
- Key: Name
Value: !Sub ${EnvName}-k8s-worker-subnet-rtb
K8sOperatorRouteTable: # For master & worker subet
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref K8sVPC
Tags:
- Key: Name
Value: !Sub ${EnvName}-k8s-operator-subnet-rtb
# Association
BastionPublicRtbAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref BastionPublicSubnet
RouteTableId: !Ref BastionPublicSubnetRouteTable
K8sOperatorRtbAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref K8sOperatorSubnet
RouteTableId: !Ref K8sOperatorRouteTable
K8sWorkerAZ1RtbAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref K8sWorkerSubnet1
RouteTableId: !Ref K8sWorkerRouteTable
K8sWorkerAZ2RtbAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref K8sWorkerSubnet2
RouteTableId: !Ref K8sWorkerRouteTable
# Route
BastionPublicToIGWRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: '0.0.0.0/0'
RouteTableId: !Ref BastionPublicSubnetRouteTable
GatewayId:
!Ref BastionInternetGateway
DependsOn: AttachBastionInternetGateway
BastionPublicToK8sOperatorRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: !Ref K8sOperatorSubnetCIDR
RouteTableId: !Ref BastionPublicSubnetRouteTable
VpcPeeringConnectionId: !Ref VPCPeering
K8sOperatorToBastionRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: !Ref BastionVPCCIDR
RouteTableId: !Ref K8sOperatorRouteTable
VpcPeeringConnectionId: !Ref VPCPeering
################################################################################
### Security Group & Rule ######################################################
################################################################################
##### Bastion Public SG #####
BastionPublicSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub ${EnvName}-bastion-public-sg
GroupDescription: SG for public subnet instance
Tags:
- Key: Name
Value: !Sub ${EnvName}-bastion-public-sg
VpcId: !Ref BastionVPC
BastionPublicIngressFromYourComputerSSH:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: From Your Computer to Public
GroupId: !GetAtt BastionPublicSecurityGroup.GroupId
FromPort: 22
ToPort: 22
IpProtocol: tcp
CidrIp: !Sub ${YourComputerIPAddress}/32
DependsOn: BastionPublicSecurityGroup
BastionPublicIngressFromYourComputerRDP:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: From Your Computer to Public
GroupId: !GetAtt BastionPublicSecurityGroup.GroupId
FromPort: 3389
ToPort: 3389
IpProtocol: tcp
CidrIp: !Sub ${YourComputerIPAddress}/32
DependsOn: BastionPublicSecurityGroup
BastionPublicIngressFromK8sOperator:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: From K8s Operator to Bastion Public
GroupId: !GetAtt BastionPublicSecurityGroup.GroupId
FromPort: -1 # All port
ToPort: -1
IpProtocol: -1 # All traffic
CidrIp: !Ref K8sOperatorSubnetCIDR
DependsOn: BastionPublicSecurityGroup
#### K8s Operator SG #####
K8sOperatorSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub ${EnvName}-k8s-operator-sg
GroupDescription: SG for K8s operator
Tags:
- Key: Name
Value: !Sub ${EnvName}-k8s-operator-sg
VpcId: !Ref K8sVPC
K8sOperatorIngressFromK8s:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: From K8sVPC to Control
GroupId: !GetAtt K8sOperatorSecurityGroup.GroupId
FromPort: -1 # All port
ToPort: -1
IpProtocol: -1 # All traffic
CidrIp: !Ref K8sVPCCIDR
DependsOn: K8sOperatorSecurityGroup
K8sOperatorIngressFromBastion:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: From Closed to Worker
GroupId: !GetAtt K8sOperatorSecurityGroup.GroupId
FromPort: -1 # All port
ToPort: -1
IpProtocol: -1 # All traffic
CidrIp: !Ref BastionVPCCIDR
DependsOn: K8sOperatorSecurityGroup
################################################################################
### VPC Endpoint ###############################################################
################################################################################
# VPC Endpoints Security Group for K8s VPCs
VPCEndpointSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub ${EnvName}-vpce-sg
GroupDescription: SG for VPC endpoints
Tags:
- Key: Name
Value: !Sub ${EnvName}-vpce-sg
VpcId: !Ref K8sVPC
VPCEndpointSGRules:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: From k8s vpc to private links
GroupId: !GetAtt VPCEndpointSG.GroupId
IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: !Ref K8sVPCCIDR
DependsOn: VPCEndpointSG
S3Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
VpcEndpointType: Gateway
VpcId: !Ref K8sVPC
RouteTableIds:
- !Ref K8sWorkerRouteTable
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: AmazonLinuxYumUpdatePolicy
Effect: Allow
Principal: '*'
Action:
- s3:Get*
Resource:
- !Sub arn:aws:s3:::packages.${AWS::Region}.amazonaws.com/*
- !Sub arn:aws:s3:::repo.${AWS::Region}.amazonaws.com/*
- !Sub arn:aws:s3:::amazonlinux.${AWS::Region}.amazonaws.com/*
- Sid: AllowPullFromECRPolicy
Effect: Allow
Principal: '*'
Action:
- s3:Get*
Resource:
- !Sub arn:aws:s3:::prod-${AWS::Region}-starport-layer-bucket/*
EC2Endpoint: # Kubelet with aws cloud provider requires ec2 endpoint access
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2
VpcEndpointType: Interface # Private Link
VpcId: !Ref K8sVPC
SubnetIds:
- !Ref K8sWorkerSubnet1
SecurityGroupIds:
- !Ref VPCEndpointSG
PrivateDnsEnabled: True
ECRDKREndpoint: # WorkerNodes requires pull containers from ECR
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub com.amazonaws.${AWS::Region}.ecr.dkr # Image Endpoisnt
VpcEndpointType: Interface # Private Link
VpcId: !Ref K8sVPC
SubnetIds:
- !Ref K8sWorkerSubnet1
SecurityGroupIds:
- !Ref VPCEndpointSG
PrivateDnsEnabled: True
ECRAPIEndpoint: # WorkerNodes requires pull containers from ECR
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub com.amazonaws.${AWS::Region}.ecr.api # Control Endpoint
VpcEndpointType: Interface # Private Link
VpcId: !Ref K8sVPC
SubnetIds:
- !Ref K8sWorkerSubnet1
SecurityGroupIds:
- !Ref VPCEndpointSG
PrivateDnsEnabled: True
################################################################################
### IAM ########################################################################
################################################################################
# Role for EKS
# This Role should be created for each cluster,
# however aws doesn't allow custom policy for eks role.
# so, to simplify, create 1 role managing multiple clustors
AWSServiceRoleForAmazonEKS:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${EnvName}-eks-service-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- eks.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonEKSServicePolicy # EKS requires aws managed policy...
- arn:aws:iam::aws:policy/AmazonEKSClusterPolicy # EKS requires aws managed policy...
# K8s operator role
K8sAdminRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${EnvName}-admin-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
K8sAdminInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /Minaden/DataProcessing/
Roles:
- Ref: K8sAdminRole
InstanceProfileName: !Sub ${EnvName}-k8s-admin-instance-profile # Limitation in length
################################################################################
### EC2 Instance ###############################################################
################################################################################
BastionEIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
BastionEIPAssociate:
Type: AWS::EC2::EIPAssociation
Properties:
AllocationId: !GetAtt BastionEIP.AllocationId
InstanceId: !Ref Bastion
Bastion:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref AmiId
InstanceType: !Ref InstanceType
BlockDeviceMappings:
- DeviceName: !Ref RootVolumePath
Ebs:
VolumeSize: !Ref RootVolumeSize
Encrypted: true
DeleteOnTermination: true
KeyName: !Ref KeyName
SecurityGroupIds:
- !GetAtt BastionPublicSecurityGroup.GroupId
Tags:
- Key: Name
Value: !Sub ${EnvName}-bastion
SubnetId: !Ref BastionPublicSubnet
CreditSpecification:
CPUCredits: standard # Disable T2/3 Unlimited
IamInstanceProfile: !Ref K8sAdminInstanceProfile
Operator:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref AmiId
InstanceType: !Ref InstanceType
BlockDeviceMappings:
- DeviceName: !Ref RootVolumePath
Ebs:
VolumeSize: !Ref RootVolumeSize
Encrypted: true
DeleteOnTermination: true
KeyName: !Ref KeyName
SecurityGroupIds:
- !GetAtt K8sOperatorSecurityGroup.GroupId
Tags:
- Key: Name
Value: !Sub ${EnvName}-operator
SubnetId: !Ref K8sOperatorSubnet
CreditSpecification:
CPUCredits: standard # Disable T2/3 Unlimited
IamInstanceProfile: !Ref K8sAdminInstanceProfile
Outputs:
K8sVPCID:
Description: The ID of K8s VPC
Value: !Ref K8sVPC
K8sWorkerSubnet1:
Description: The ID of K8s worker subnet for main use
Value: !Ref K8sWorkerSubnet1
K8sWorkerSubnet2:
Description: The ID of K8s worker subnet for sub use
Value: !Ref K8sWorkerSubnet2
K8sOperatorSubnet:
Description: The ID of K8s operator subnet
Value: !Ref K8sOperatorSubnet
K8sOperatorSecurityGroup:
Description: SG for K8s Operator
Value: !Ref K8sOperatorSecurityGroup
RoleArn:
Description: The role that EKS will use to create AWS resources for Kubernetes clusters
Value: !GetAtt AWSServiceRoleForAmazonEKS.Arn
2-1 Control Plane SG
1に統合してもいいのですが、クラスターセキュリティグループの考慮事項 - Amazon EKSによるとクラスタごとにSGは分けた方がいいと記述されています。今回は1つのみですが、複数クラスタを作成することを考慮し分けています。
(実際のうちの環境では、コンポーネントごとにEKSクラスタを分離しています。)
`2-1-controlplane-sg.yaml`
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
EnvName:
Type: String
Default: yoda-close-nw
Description: Component application identifier used by this sequential deployment
ClusterName:
Type: String
Default: yoda-closed-cluster-1
Description: K8s cluster name
K8sVpcId:
Type: String
Description: K8s cluster VPCID
OperatorSgId:
Type: String
Description: Operator security group id
Resources:
K8s Cluster ControlPlane SG
ClusterControlPlaneSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub ${EnvName}-${ClusterName}-controlplane-sg
GroupDescription: SG for control plane (k8s endpoint)
Tags:
- Key: Name
Value: !Sub ${EnvName}-${ClusterName}-controlplane-sg
VpcId: !Ref K8sVpcId
ClusterControlPlaneSecurityGroupIngressFromOperator:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: Allow operator to communicate with the cluster API Server
GroupId: !Ref ClusterControlPlaneSecurityGroup
SourceSecurityGroupId: !Ref OperatorSgId
IpProtocol: tcp
ToPort: 443
FromPort: 443
</div></details>
#### 2-2 EKS
<img width="600" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320408/41f558b8-c627-f74c-f026-f2b57656ada8.png">
CloudFormationでは現在、Private AccessをEnableにできないのでCLIで作成します。 [^3]
#### 2-3 Worker Nodes
<img width="600" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320408/c770a9ca-8048-0827-6edf-67a1369c2b40.png">
AWS提供のテンプレートをいくつか改修してあります(PublicIPを削除する、EBS暗号化入れるなど)
UserDataのセクションで`bootstrap.sh`を起動しているのが見えるかと思います。
<details><summary>`2-3-workers.yaml`</summary><div>
```yaml:2-3-workers.yaml
# Based on https://github.com/awslabs/amazon-eks-ami/blob/master/amazon-eks-nodegroup.yaml
# Add
# - Default Value for NodeImageId
# - Parameter for EnvName
# - Name tag for worker sg
# - Name for worker IAM role
# - Parameter for operator sg
# - SG rule from operator
# - EBS encryption enable
# Change
# - Default Value for AutoScalingCapacities (to be single nodes)
# - Disable Public IP for workers
---
AWSTemplateFormatVersion: 2010-09-09
Description: Amazon EKS - Node Group
Parameters:
KeyName:
Description: The EC2 Key Pair to allow SSH access to the instances
Type: AWS::EC2::KeyPair::KeyName
NodeImageId:
Description: AMI id for the node instances.
Type: AWS::EC2::Image::Id
Default: ami-04c0f02f5e148c80a
NodeInstanceType:
Description: EC2 instance type for the node instances
Type: String
Default: t3.medium
ConstraintDescription: Must be a valid EC2 instance type
AllowedValues:
- a1.medium
- a1.large
- a1.xlarge
- a1.2xlarge
- a1.4xlarge
- c1.medium
- c1.xlarge
- c3.large
- c3.xlarge
- c3.2xlarge
- c3.4xlarge
- c3.8xlarge
- c4.large
- c4.xlarge
- c4.2xlarge
- c4.4xlarge
- c4.8xlarge
- c5.large
- c5.xlarge
- c5.2xlarge
- c5.4xlarge
- c5.9xlarge
- c5.18xlarge
- c5d.large
- c5d.xlarge
- c5d.2xlarge
- c5d.4xlarge
- c5d.9xlarge
- c5d.18xlarge
- c5n.large
- c5n.xlarge
- c5n.2xlarge
- c5n.4xlarge
- c5n.9xlarge
- c5n.18xlarge
- cc2.8xlarge
- cr1.8xlarge
- d2.xlarge
- d2.2xlarge
- d2.4xlarge
- d2.8xlarge
- f1.2xlarge
- f1.4xlarge
- f1.16xlarge
- g2.2xlarge
- g2.8xlarge
- g3s.xlarge
- g3.4xlarge
- g3.8xlarge
- g3.16xlarge
- h1.2xlarge
- h1.4xlarge
- h1.8xlarge
- h1.16xlarge
- hs1.8xlarge
- i2.xlarge
- i2.2xlarge
- i2.4xlarge
- i2.8xlarge
- i3.large
- i3.xlarge
- i3.2xlarge
- i3.4xlarge
- i3.8xlarge
- i3.16xlarge
- i3.metal
- i3en.large
- i3en.xlarge
- i3en.2xlarge
- i3en.3xlarge
- i3en.6xlarge
- i3en.12xlarge
- i3en.24xlarge
- m1.small
- m1.medium
- m1.large
- m1.xlarge
- m2.xlarge
- m2.2xlarge
- m2.4xlarge
- m3.medium
- m3.large
- m3.xlarge
- m3.2xlarge
- m4.large
- m4.xlarge
- m4.2xlarge
- m4.4xlarge
- m4.10xlarge
- m4.16xlarge
- m5.large
- m5.xlarge
- m5.2xlarge
- m5.4xlarge
- m5.12xlarge
- m5.24xlarge
- m5a.large
- m5a.xlarge
- m5a.2xlarge
- m5a.4xlarge
- m5a.12xlarge
- m5a.24xlarge
- m5ad.large
- m5ad.xlarge
- m5ad.2xlarge
- m5ad.4xlarge
- m5ad.12xlarge
- m5ad.24xlarge
- m5d.large
- m5d.xlarge
- m5d.2xlarge
- m5d.4xlarge
- m5d.12xlarge
- m5d.24xlarge
- p2.xlarge
- p2.8xlarge
- p2.16xlarge
- p3.2xlarge
- p3.8xlarge
- p3.16xlarge
- p3dn.24xlarge
- r3.large
- r3.xlarge
- r3.2xlarge
- r3.4xlarge
- r3.8xlarge
- r4.large
- r4.xlarge
- r4.2xlarge
- r4.4xlarge
- r4.8xlarge
- r4.16xlarge
- r5.large
- r5.xlarge
- r5.2xlarge
- r5.4xlarge
- r5.12xlarge
- r5.24xlarge
- r5a.large
- r5a.xlarge
- r5a.2xlarge
- r5a.4xlarge
- r5a.12xlarge
- r5a.24xlarge
- r5ad.large
- r5ad.xlarge
- r5ad.2xlarge
- r5ad.4xlarge
- r5ad.12xlarge
- r5ad.24xlarge
- r5d.large
- r5d.xlarge
- r5d.2xlarge
- r5d.4xlarge
- r5d.12xlarge
- r5d.24xlarge
- t1.micro
- t2.nano
- t2.micro
- t2.small
- t2.medium
- t2.large
- t2.xlarge
- t2.2xlarge
- t3.nano
- t3.micro
- t3.small
- t3.medium
- t3.large
- t3.xlarge
- t3.2xlarge
- t3a.nano
- t3a.micro
- t3a.small
- t3a.medium
- t3a.large
- t3a.xlarge
- t3a.2xlarge
- x1.16xlarge
- x1.32xlarge
- x1e.xlarge
- x1e.2xlarge
- x1e.4xlarge
- x1e.8xlarge
- x1e.16xlarge
- x1e.32xlarge
- z1d.large
- z1d.xlarge
- z1d.2xlarge
- z1d.3xlarge
- z1d.6xlarge
- z1d.12xlarge
NodeAutoScalingGroupMinSize:
Description: Minimum size of Node Group ASG.
Type: Number
Default: 1
NodeAutoScalingGroupMaxSize:
Description: Maximum size of Node Group ASG. Set to at least 1 greater than NodeAutoScalingGroupDesiredCapacity.
Type: Number
Default: 1
NodeAutoScalingGroupDesiredCapacity:
Description: Desired capacity of Node Group ASG.
Type: Number
Default: 1
NodeVolumeSize:
Description: Node volume size
Type: Number
Default: 20
ClusterName:
Description: The cluster name provided when the cluster was created. If it is incorrect, nodes will not be able to join the cluster.
Type: String
BootstrapArguments:
Description: Arguments to pass to the bootstrap script. See files/bootstrap.sh in https://github.com/awslabs/amazon-eks-ami
Type: String
Default: ""
NodeGroupName:
Description: Unique identifier for the Node Group.
Type: String
ClusterControlPlaneSecurityGroup:
Description: The security group of the cluster control plane.
Type: AWS::EC2::SecurityGroup::Id
VpcId:
Description: The VPC of the worker instances
Type: AWS::EC2::VPC::Id
Subnets:
Description: The subnets where workers can be created.
Type: List<AWS::EC2::Subnet::Id>
EnvName:
Type: String
Default: yoda-close-nw
Description: Component application identifier used by this sequential deployment
OperatorSgId:
Description: Operator security group id
Type: String
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: EKS Cluster
Parameters:
- ClusterName
- ClusterControlPlaneSecurityGroup
- Label:
default: Worker Node Configuration
Parameters:
- NodeGroupName
- NodeAutoScalingGroupMinSize
- NodeAutoScalingGroupDesiredCapacity
- NodeAutoScalingGroupMaxSize
- NodeInstanceType
- NodeImageId
- NodeVolumeSize
- KeyName
- BootstrapArguments
- Label:
default: Worker Network Configuration
Parameters:
- VpcId
- Subnets
Resources:
NodeInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: "/"
Roles:
- !Ref NodeInstanceRole
NodeInstanceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${EnvName}-${ClusterName}-worker-role
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
Path: "/"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
- arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
- arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
NodeSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for all nodes in the cluster
VpcId: !Ref VpcId
Tags:
- Key: !Sub kubernetes.io/cluster/${ClusterName}
Value: owned
- Key: Name
Value: !Sub ${EnvName}-${ClusterName}-worker-sg
NodeSecurityGroupIngress:
Type: AWS::EC2::SecurityGroupIngress
DependsOn: NodeSecurityGroup
Properties:
Description: Allow node to communicate with each other
GroupId: !Ref NodeSecurityGroup
SourceSecurityGroupId: !Ref NodeSecurityGroup
IpProtocol: -1
FromPort: 0
ToPort: 65535
NodeSecurityGroupFromControlPlaneIngress:
Type: AWS::EC2::SecurityGroupIngress
DependsOn: NodeSecurityGroup
Properties:
Description: Allow worker Kubelets and pods to receive communication from the cluster control plane
GroupId: !Ref NodeSecurityGroup
SourceSecurityGroupId: !Ref ClusterControlPlaneSecurityGroup
IpProtocol: tcp
FromPort: 1025
ToPort: 65535
ControlPlaneEgressToNodeSecurityGroup:
Type: AWS::EC2::SecurityGroupEgress
DependsOn: NodeSecurityGroup
Properties:
Description: Allow the cluster control plane to communicate with worker Kubelet and pods
GroupId: !Ref ClusterControlPlaneSecurityGroup
DestinationSecurityGroupId: !Ref NodeSecurityGroup
IpProtocol: tcp
FromPort: 1025
ToPort: 65535
NodeSecurityGroupFromControlPlaneOn443Ingress:
Type: AWS::EC2::SecurityGroupIngress
DependsOn: NodeSecurityGroup
Properties:
Description: Allow pods running extension API servers on port 443 to receive communication from cluster control plane
GroupId: !Ref NodeSecurityGroup
SourceSecurityGroupId: !Ref ClusterControlPlaneSecurityGroup
IpProtocol: tcp
FromPort: 443
ToPort: 443
NodeSecurityGroupFromOperatorOn22Ingress:
Type: AWS::EC2::SecurityGroupIngress
DependsOn: NodeSecurityGroup
Properties:
Description: Allow nodes to communicate with the operator on SSH
GroupId: !Ref NodeSecurityGroup
SourceSecurityGroupId: !Ref OperatorSgId
IpProtocol: tcp
FromPort: 22
ToPort: 22
NodeSecurityGroupFromOperatorOn80Ingress:
Type: AWS::EC2::SecurityGroupIngress
DependsOn: NodeSecurityGroup
Properties:
Description: Allow nodes to communicate with the operator on HTTP
GroupId: !Ref NodeSecurityGroup
SourceSecurityGroupId: !Ref OperatorSgId
IpProtocol: tcp
FromPort: 80
ToPort: 80
ControlPlaneEgressToNodeSecurityGroupOn443:
Type: AWS::EC2::SecurityGroupEgress
DependsOn: NodeSecurityGroup
Properties:
Description: Allow the cluster control plane to communicate with pods running extension API servers on port 443
GroupId: !Ref ClusterControlPlaneSecurityGroup
DestinationSecurityGroupId: !Ref NodeSecurityGroup
IpProtocol: tcp
FromPort: 443
ToPort: 443
ClusterControlPlaneSecurityGroupIngress:
Type: AWS::EC2::SecurityGroupIngress
DependsOn: NodeSecurityGroup
Properties:
Description: Allow pods to communicate with the cluster API Server
GroupId: !Ref ClusterControlPlaneSecurityGroup
SourceSecurityGroupId: !Ref NodeSecurityGroup
IpProtocol: tcp
ToPort: 443
FromPort: 443
NodeGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
DesiredCapacity: !Ref NodeAutoScalingGroupDesiredCapacity
LaunchConfigurationName: !Ref NodeLaunchConfig
MinSize: !Ref NodeAutoScalingGroupMinSize
MaxSize: !Ref NodeAutoScalingGroupMaxSize
VPCZoneIdentifier: !Ref Subnets
Tags:
- Key: Name
Value: !Sub ${ClusterName}-${NodeGroupName}-Node
PropagateAtLaunch: true
- Key: !Sub kubernetes.io/cluster/${ClusterName}
Value: owned
PropagateAtLaunch: true
UpdatePolicy:
AutoScalingRollingUpdate:
MaxBatchSize: 1
MinInstancesInService: !Ref NodeAutoScalingGroupDesiredCapacity
PauseTime: PT5M
NodeLaunchConfig:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
AssociatePublicIpAddress: false
IamInstanceProfile: !Ref NodeInstanceProfile
ImageId: !Ref NodeImageId
InstanceType: !Ref NodeInstanceType
KeyName: !Ref KeyName
SecurityGroups:
- !Ref NodeSecurityGroup
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
VolumeSize: !Ref NodeVolumeSize
VolumeType: gp2
DeleteOnTermination: true
Encrypted: true
UserData:
Fn::Base64:
!Sub |
#!/bin/bash
set -o xtrace
/etc/eks/bootstrap.sh ${ClusterName} ${BootstrapArguments}
/opt/aws/bin/cfn-signal --exit-code $? \
--stack ${AWS::StackName} \
--resource NodeGroup \
--region ${AWS::Region}
Outputs:
NodeInstanceRole:
Description: The node instance role
Value: !GetAtt NodeInstanceRole.Arn
NodeSecurityGroup:
Description: The security group for the node group
Value: !Ref NodeSecurityGroup
起動後のセットアップ
Worker Nodeを起動しただけでは、EKSからはNodeとして認識されるもののNotReady
の状態となります。
そのためaws-auth
というConfigMap(Kubernetesにおける設定ファイルのような概念)を作成・適用する必要があります。
また、VPC内にのOperatorからkubectlを打てるように、Operatorへ紐付けたIAMロールもaws-auth
に追加する必要があります。
さらに環境変数等設定せずにkubectlを打つためには、${HOME}/.kube/config
以下に設定が記載されたファイルを保持する必要があります。
参考スクリプトでは、scpでクライアントPCから転送します。クライアントのssh_configへと追記を行うようにしていますが、こいじるのが嫌な場合は適宜修正してください。
`create_cluster.sh`
#!/usr/bin/env bash
set -o pipefail # Stop pipe when error occurred
set -o nounset # Check undefined variables
set -o errexit # Exit when error occurred
set -o xtrace # Print debug information
################################################################################
### Variables ##################################################################
################################################################################
ENV_NAME=${ENV_NAME:-"yoda-eks-closed"}
STACK_NAME_BASE=${STACK_NAME_BASE:-$ENV_NAME}
CLUSTER_NAME=${CLUSTER_NAME:-"yoda-k8s-1"}
K8S_VERSION=${K8S_VERSION:-"1.13"}
KEY_NAME=${KEY_NAME:-"yoda-key"}
NODE_INSTANCE_TYPE=${NODE_INSTANCE_TYPE:-"t3.small"}
OPERATOR_AMI_ID=${OPERATOR_AMI_ID:-"ami-03dc8213c70f79358"}
YOUR_COMPUTER_IPADDR=${YOUR_COMPUTER_IPADDR:-$(curl inet-ip.info)}
################################################################################
### 1 Infrastructure ###########################################################
################################################################################
STACK_NAME=${STACK_NAME_BASE}-infrastructure
TEMPLATE=${TEMPLATE_1:-"file://templates/1-infrastructure.yaml"}
aws cloudformation create-stack \
--stack-name ${STACK_NAME} \
--template-body ${TEMPLATE} \
--parameters \
ParameterKey=EnvName,ParameterValue=${ENV_NAME} \
ParameterKey=YourComputerIPAddress,ParameterValue=${YOUR_COMPUTER_IPADDR} \
ParameterKey=KeyName,ParameterValue=${KEY_NAME} \
ParameterKey=AmiId,ParameterValue=${OPERATOR_AMI_ID} \
--capabilities CAPABILITY_NAMED_IAM
aws cloudformation wait stack-create-complete --stack-name ${STACK_NAME}
################################################################################
### 2-1 Control Plane SG #######################################################
################################################################################
STACK_NAME=${STACK_NAME_BASE}-${CLUSTER_NAME}-cplane-sg
TEMPLATE=${TEMPLATE_2_1:-"file://templates/2-1-controlplane-sg.yaml"}
K8S_VPC_ID=$(aws ec2 describe-vpcs \
--filters Name=tag:Name,Values=${ENV_NAME}-k8s-vpc \
--query "Vpcs[*].VpcId" \
--output text)
OPERATOR_SG_ID=$(aws ec2 describe-security-groups \
--filter Name=group-name,Values=${ENV_NAME}-k8s-operator-sg \
--query "SecurityGroups[*].GroupId" \
--output text)
aws cloudformation create-stack \
--stack-name ${STACK_NAME} \
--template-body ${TEMPLATE} \
--parameters \
ParameterKey=EnvName,ParameterValue=${ENV_NAME} \
ParameterKey=ClusterName,ParameterValue=${CLUSTER_NAME} \
ParameterKey=K8sVpcId,ParameterValue=${K8S_VPC_ID} \
ParameterKey=OperatorSgId,ParameterValue=${OPERATOR_SG_ID}
aws cloudformation wait stack-create-complete --stack-name ${STACK_NAME}
################################################################################
### 2-2 EKS ####################################################################
################################################################################
EKS_SERVICE_ROLE_ARN=$(aws iam get-role \
--role-name ${ENV_NAME}-eks-service-role \
--query "Role.Arn" \
--output text)
SUBNET_IDS=$(aws ec2 describe-subnets \
--filter "Name=tag:Name,Values=${ENV_NAME}-k8s-worker*" \
--query "Subnets[*].SubnetId" \
--output text |
tr '\t' ',')
CPLANE_SG_ID=$(aws ec2 describe-security-groups \
--filter Name=group-name,Values=${ENV_NAME}-${CLUSTER_NAME}-controlplane-sg \
--query "SecurityGroups[*].GroupId" \
--output text)
aws eks create-cluster \
--name ${CLUSTER_NAME} \
--role-arn ${EKS_SERVICE_ROLE_ARN} \
--resources-vpc-config \
subnetIds=${SUBNET_IDS},securityGroupIds=${CPLANE_SG_ID},endpointPublicAccess=true,endpointPrivateAccess=true \
--kubernetes-version=${K8S_VERSION}
aws eks wait cluster-active --name ${CLUSTER_NAME}
# Download kubeconfig to /root/.kube
aws eks update-kubeconfig --name ${CLUSTER_NAME}
kubectl get svc # Check Auth
################################################################################
### 2-3 Worker Nodes ###########################################################
################################################################################
STACK_NAME=${STACK_NAME_BASE}-${CLUSTER_NAME}-workers
TEMPLATE=${TEMPLATE_2_3:-"file://templates/2-3-workers.yaml"}
K8S_ENDPOINT=$(aws eks describe-cluster \
--name ${CLUSTER_NAME} \
--query "cluster.endpoint" \
--output text)
K8S_CA=$(aws eks describe-cluster \
--name ${CLUSTER_NAME} \
--query "cluster.certificateAuthority.data" \
--output text)
MAIN_SUBNET_ID=$(aws ec2 describe-subnets \
--filter "Name=tag:Name,Values=${ENV_NAME}-k8s-worker-subnet-1" \
--query "Subnets[*].SubnetId" \
--output text)
aws cloudformation create-stack \
--stack-name ${STACK_NAME} \
--template-body ${TEMPLATE} \
--parameters \
ParameterKey=KeyName,ParameterValue=${KEY_NAME} \
ParameterKey=NodeInstanceType,ParameterValue=${NODE_INSTANCE_TYPE} \
ParameterKey=ClusterName,ParameterValue=${CLUSTER_NAME} \
ParameterKey=BootstrapArguments,ParameterValue="--apiserver-endpoint ${K8S_ENDPOINT} --b64-cluster-ca ${K8S_CA}" \
ParameterKey=NodeGroupName,ParameterValue=${CLUSTER_NAME}-worker \
ParameterKey=ClusterControlPlaneSecurityGroup,ParameterValue=${CPLANE_SG_ID} \
ParameterKey=VpcId,ParameterValue=${K8S_VPC_ID} \
ParameterKey=Subnets,ParameterValue=${MAIN_SUBNET_ID} \
ParameterKey=EnvName,ParameterValue=${ENV_NAME} \
ParameterKey=OperatorSgId,ParameterValue=${OPERATOR_SG_ID} \
--capabilities CAPABILITY_NAMED_IAM
aws cloudformation wait stack-create-complete --stack-name ${STACK_NAME}
# Apply AWS Authenticator ConfigMap
WORKER_ROLE_ARN=$(aws iam get-role \
--role-name ${ENV_NAME}-${CLUSTER_NAME}-worker-role \
--query "Role.Arn" \
--output text)
K8S_ADMIN_ROLE_ARN=$(aws iam get-role \
--role-name ${ENV_NAME}-admin-role \
--query "Role.Arn" \
--output text)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: aws-auth
namespace: kube-system
data:
mapRoles: |
- rolearn: ${WORKER_ROLE_ARN}
username: system:node:{{EC2PrivateDNSName}}
groups:
- system:bootstrappers
- system:nodes
- rolearn: ${K8S_ADMIN_ROLE_ARN}
username: system:node:{{EC2PrivateDNSName}}
groups:
- system:masters
EOF
################################################################################
### 3 Configuration ############################################################
################################################################################
# Assemble .ssh/config
BASTION_IP=$(aws ec2 describe-instances \
--filter Name=tag:Name,Values=${ENV_NAME}-bastion \
--query "Reservations[*].Instances[*].PublicIpAddress" \
--output text)
OPERATOR_IP=$(aws ec2 describe-instances \
--filter Name=tag:Name,Values=${ENV_NAME}-operator \
--query "Reservations[*].Instances[*].PrivateIpAddress" \
--output text)
cat <<EOF > ~/.ssh/config
Host *
IdentitiesOnly yes
ServerAliveInterval 120
ServerAliveCountMax 10
ForwardAgent yes
User ec2-user
IdentityFile ~/.ssh/${KEY_NAME}.pem
TCPKeepAlive yes
Host bastion
HostName ${BASTION_IP}
Host operator
HostName ${OPERATOR_IP}
Localforward 0.0.0.0:8765 localhost:8765
ProxyCommand ssh -W %h:%p bastion
EOF
# Create .kube and transport config file to bastion
ssh -oStrictHostKeyChecking=no bastion \
mkdir /home/ec2-user/.kube
scp -oStrictHostKeyChecking=no ~/.kube/config bastion:/home/ec2-user/.kube/
# Create .kube and transport config file to operator
ssh -oStrictHostKeyChecking=no operator \
mkdir /home/ec2-user/.kube
scp -oStrictHostKeyChecking=no ~/.kube/config operator:/home/ec2-user/.kube/
# Enable SSH Agent (To log-in to worker nodes from this container)
eval `ssh-agent`
ssh-add ~/.ssh/${KEY_NAME}.pem
echo "Creation Completed."
先にあげたテンプレートを作業ディレクトリ(仮に/templetes
とします)に
1-infrastructure.yaml
2-1-controlplane-sg.yaml
2-3-workers.yaml
と配置して、スクリプトを走らせると通しで環境作成がされます(Variables
は適宜変更ください)。
運用について
VPCエンドポイントについて
構築時に必要になるVPCエンドポイントですが、それ以降は必要にならないので、セキュリティ面で気になる場合は外してしまっても問題ありません。
ただし、AutoScalingが組まれるので、VPCエンドポイントをそのままにしておけば
- AutoScalingの値を調整するだけでノード増減が可能
- EC2の障害発生時も自動で新しいノードに挿しかわる
ため、つけておくと何かと便利です。
またS3, ECRはVPCエンドポイントポリシーとリソースのポリシーの両方を持ち合わせているため、内部からの流出経路となりうるバケットやリポジトリに対し、強固な制限 が可能です(e.g. 他のアカウントのアクセスキーを持ち込まれてもエンドポイントポリシーで拒否する、リソースのポリシーで特定VPCエンドポイントからの操作のみ受け付けるようにする、など)。
コンテナの持ち込み
いくつか手段がありますが、
- ECRに直接Push
-
docer export
を利用し、tar
で固めたイメージをS3経由で転送
が簡便です。双方上述の通りリソースポリシー、VPCエンドポイントポリシーを持つので持ち込み限定のリポジトリやバケットの作成が可能です。
前者が簡便ですが、後者の方がログや他の資材の持ち込みも可能、と言った長短があります。
EKSの困ったところ
自動アップデート
https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/platform-versions.html
にあるように、EKSの対応するkubernetesの、マイナーバージョンがアップデートが行われた場合自動でアップデートされます。そのため EKSの上でSparkアプリケーションをを動かしていたのですがある日突然停止 するなんてことも……
(こちらの事象だったので一部jarを差し替えてことなきを得ました)
レアケースだとは思いますが、気をつけておいた方がいいポイントです。
PodとPrivate IPの1:1対応
こちらはよく言われていることですが、EKSではPodに対しVPC内でワークするPrivateIPを割り当てます。そのため
- VPCのレンジを広めに
- NodeのEC2のサイズは大きめ(EC2に割り当て可能なENIの上限はインスタンスサイズで決まるため、それぞれの上限数はこちら)
にしておかないと追加で新しいPrivate IPが払い出せない=新しいPodの追加ができない、ことになるため難儀します。
慣れるまでは大変でしたが、現在はコンテナの便利さを十分に受けれてるんでないかなーと。KubernetesやEKSユーザーの助けになれば幸いです。
-
1月の第三者認証対応や、6月のECR VPCEndpoint対応など、一気にEKSが利用しやすい環境が整った2019年でした! ↩
-
今年のKubeConでManaged Nodeが発表されたため、正確には全てを管理するサービスとなっています。 ↩