見出し画像

k8sマルチマスター をEC2 by Cloudformationでやってみた

仕事でk8sに触れる機会はあったものの、マスターノードはフルマネージドサービスを利用するのが当たり前なため、どのような仕組みでマスターノードが動いているのかほとんど分からないまま使っていました。そこで、少しでも理解を深めるため、kubeadmを使ってVMノード(EC2)上にk8sマルチマスタを構築してみました。

また、せっかくAWSを利用するならこれまで触る機会が少なかったCloudformationを使ってインフラ周りを構築しようということで、簡単なテンプレートを作成しました。これからCloudformationを学習しようとしている方に少しでも参考になれば幸いです。


今回はやってみた記事なのですが、Cloudformationを使ってインフラ構築した部分以外は以下の記事をほぼほぼ参考にさせていただいてますので、k8sクラスタ構築に関わる詳細は以下を参考にしていただければと思います。

全体構成

画像1

・ハードウェア構成:
 - EC2(LB用)×1
 - EC2(k8s master node用)×3
 - EC2(k8s worker node用)×1
 ※OSはAmazon linux 2を使用

・LBにはHAProxyを利用

・k8sの構築にはkubeadmを使用

・CNIにはCalicoを使用

・AWS CLIおよびシークレットキー設定済みのラップトップ環境(Windows Subsystem for Linux 2(ubuntu18.04))からCloudformationを実行
※今回は管理者権限をもつシークレットキーを利用したため、IAM周りの細かい権限説明はしません

Cloudformation解説

ソースコードは以下にあります。

今回作成したテンプレートファイルについて簡単に解説します。

AWSTemplateFormatVersion: "2010-09-09"
Description: Provision EC2 for k8s

Parameters:
 KeyName:
   Description: The EC2 Key Pair to allow SSH Access to the instance
   Type: "AWS::EC2::KeyPair::KeyName"
 MyIP:
   Description: IP address allowed to access EC2
   Type: String
 Ec2ImageId:
   Type: AWS::SSM::Parameter::Value<String>
   Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
 Ec2InstanceType:
   Type: String
   Default: t3.small

テンプレートファイルのパラメータ部分です。Cloudformation stack作成時に値を指定する必要があります。
・KeyName:EC2作成に必要なキーペア名
・MyIP:ラップトップからEC2インスタンスにSSHアクセスする際に穴あけ必要なIPアドレス

※stack作成時の参考コマンドは後述


Resources:
 VPC:
   Type: AWS::EC2::VPC
   Properties:
     CidrBlock: 10.5.0.0/16
     Tags:
       - Key: Name
         Value: vpc-cf

 IGW:
   Type: AWS::EC2::InternetGateway
   Properties:
     Tags:
       - Key: Name
         Value: igw-cf

 # IGWをVPCにアタッチ
 AttachGateway:
   Type: AWS::EC2::VPCGatewayAttachment
   Properties:
     VpcId: !Ref VPC
     InternetGatewayId: !Ref IGW

 PubSubA:
   Type: AWS::EC2::Subnet
   Properties:
     AvailabilityZone: ap-northeast-1a
     VpcId: !Ref VPC
     CidrBlock: 10.5.10.0/24
     Tags:
       - Key: Name
         Value: pub-sub-a-cf

 PubSubRT:
   Type: AWS::EC2::RouteTable
   Properties:
     VpcId: !Ref VPC
     Tags:
       - Key: Name
         Value: pub-sub-rt-cf

 # PubSub-インターネット間のルーティング
 PubSubToInternet:
   Type: AWS::EC2::Route
   Properties:
     RouteTableId: !Ref PubSubRT
     DestinationCidrBlock: 0.0.0.0/0
     GatewayId: !Ref IGW

 # ルートテーブルをサブネットに関連付け
 AssoPubSubART:
   Type: AWS::EC2::SubnetRouteTableAssociation
   Properties:
     SubnetId: !Ref PubSubA
     RouteTableId: !Ref PubSubRT

全体構成図に記載したIPアドレス帯でVPC/SubnetおよびインターネットGW、RTテーブルを作成。


  EC2HAProxy:
   Type: AWS::EC2::Instance
   Properties:
     ImageId: !Ref Ec2ImageId
     KeyName: !Ref KeyName
     InstanceType: !Ref Ec2InstanceType
     IamInstanceProfile: !Ref EC2InstanceProfile
     NetworkInterfaces:
       - AssociatePublicIpAddress: "true"
         DeviceIndex: "0"
         SubnetId: !Ref PubSubA
         PrivateIpAddress: 10.5.10.40
         GroupSet:
           - !Ref EC2MasterSG
     UserData: !Base64 |
       #!/bin/bash
       set -euo pipefail
       # hostnameとhostsの設定
       hostnamectl set-hostname ec2-haproxy-cf
       cat <<EOF | sudo tee -a /etc/hosts
       10.5.10.11 ec2-mster-1-cf
       10.5.10.12 ec2-mster-2-cf
       10.5.10.13 ec2-mster-3-cf
       10.5.10.21 ec2-worker-1-cf
       10.5.10.40 ec2-haproxy-cf
       EOF
       # HAProxyのインストールと設定
       yum install -y haproxy
       systemctl enable haproxy
       grep -q -F 'net.ipv4.ip_nonlocal_bind=1' /etc/sysctl.conf || echo 'net.ipv4.ip_nonlocal_bind=1' >> /etc/sysctl.conf
       cat >/etc/haproxy/haproxy.cfg <<EOF
       global
           log /dev/log    local0
           log 127.0.0.1   local2
           chroot /var/lib/haproxy
           #stats socket /run/haproxy/admin.sock mode 660 level admin
           stats timeout 30s
           user haproxy
           group haproxy
           daemon
           # Default SSL material locations
           ca-base /etc/ssl/certs
           crt-base /etc/ssl/private
           # Default ciphers to use on SSL-enabled listening sockets.
           # For more information, see ciphers(1SSL). This list is from:
           #  https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
           ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
           ssl-default-bind-options no-sslv3
       defaults
           log global
           mode    tcp
           option  tcplog
           option  dontlognull
               timeout connect 5000
               timeout client  50000
               timeout server  50000
               #errorfile 400 /etc/haproxy/errors/400.http
               #errorfile 403 /etc/haproxy/errors/403.http
               #errorfile 408 /etc/haproxy/errors/408.http
               #errorfile 500 /etc/haproxy/errors/500.http
               #errorfile 502 /etc/haproxy/errors/502.http
               #errorfile 503 /etc/haproxy/errors/503.http
               #errorfile 504 /etc/haproxy/errors/504.http
       frontend k8s
           bind 10.5.10.40:6443
           default_backend k8s_backend
       backend k8s_backend
           balance roundrobin
           mode tcp
           server ec2-mster-1-cf 10.5.10.11:6443 check inter 1000
           server ec2-mster-2-cf 10.5.10.12:6443 check inter 1000
           server ec2-mster-3-cf 10.5.10.13:6443 check inter 1000
       EOF
       # rsyslogを使ったhaproxyログ出力設定
       sed -i.back -e "s:^#\(\$ModLoad imudp\)$:\1:" -e "s:^#\(\$UDPServerRun 514\)$:\1:" /etc/rsyslog.conf
       touch /etc/rsyslog.d/haproxy.conf
       cat <<"EOF" > /etc/rsyslog.d/haproxy.conf
       $ModLoad imudp
       $UDPServerRun 514
       local2.info /var/log/haproxy/haproxy.log
       local0.info /var/log/haproxy/admin.log
       # don't log anywhere else
       local0.* ~
       local2.* ~
       EOF
       # hostname設定のため再起動
       shutdown -r now
     Tags:
         - Key: Name
           Value: ec2-haproxy-cf

LB用EC2インスタンスを作成。

UserData内でHAProxyのインストールおよびセットアップ、hostsファイルの設定を行っています。

HAProxyのconfig部分は上記の参考記事の内容とほぼ変わりありませんが、HAProxyのログをファイル出力する設定を追記しています。


  # k8s master nodes
 EC2Master1: 
   Type: AWS::EC2::Instance
   Properties: 
     ImageId: !Ref Ec2ImageId
     KeyName: !Ref KeyName
     InstanceType: !Ref Ec2InstanceType
     IamInstanceProfile: !Ref EC2InstanceProfile
     NetworkInterfaces: 
       - AssociatePublicIpAddress: "true"
         DeviceIndex: "0"
         SubnetId: !Ref PubSubA
         PrivateIpAddress: 10.5.10.11
         GroupSet:
           - !Ref EC2MasterSG
     UserData: !Base64 |
       #!/bin/bash
       # hostnameとhostsの設定 
       hostnamectl set-hostname ec2-master1-cf
       cat <<EOF | sudo tee -a /etc/hosts
       10.5.10.11 ec2-mster-1-cf
       10.5.10.12 ec2-mster-2-cf
       10.5.10.13 ec2-mster-3-cf
       10.5.10.21 ec2-worker-1-cf
       10.5.10.40 ec2-haproxy-cf
       EOF
       # dockerとkubeadmのインストール
       yum install -y docker && systemctl enable docker
       cat <<EOF | sudo tee -a /etc/yum.repos.d/kubernetes.repo
       [kubernetes]
       name=Kubernetes
       baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
       enabled=1
       gpgcheck=1
       repo_gpgcheck=1
       gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
       EOF
       yum install -y kubeadm
       # hostname設定のため再起動
       shutdown -r now
     Tags:
         - Key: Name
           Value: ec2-mster-1-cf

k8sマスタノード用EC2インスタンスを作成。

UserData内でk8sマスタ構築に必要なdockerとkubeadmをインストールしています。通常、k8s構築時の手順ではswapファイルの無効化を行いますが、Amazon Linux 2はデフォルトでswapを使わない設定になっているため省略しています。

マスターノード用EC2インスタンスの2台目、3台目およびワーカーノードについても、割り当てるローカルIPアドレスとSGに差異があるのみですので、本記事ではスキップします。


  # k8s master node用SG
 EC2MasterSG:
   Type: AWS::EC2::SecurityGroup
   Properties:
     GroupName: ec2-master-sg-cf
     GroupDescription: Allow SSH and HTTP and kubernetes master access
     VpcId: !Ref VPC
     SecurityGroupIngress:
       # ssh
       - IpProtocol: tcp
         FromPort: 22
         ToPort: 22
         CidrIp: !Ref MyIP
       # k8s
       - IpProtocol: tcp
         FromPort: 6443
         ToPort: 6443
         Description: k8s API server
         CidrIp: 10.5.0.0/16
       - IpProtocol: tcp
         FromPort: 2379
         ToPort: 2379
         Description: etcd
         CidrIp: 10.5.0.0/16
       - IpProtocol: tcp
         FromPort: 2380
         ToPort: 2380
         Description: etcd
         CidrIp: 10.5.0.0/16
       - IpProtocol: tcp
         FromPort: 10250
         ToPort: 10250
         Description: kubelet
         CidrIp: 10.5.0.0/16
       - IpProtocol: tcp
         FromPort: 10251
         ToPort: 10251
         Description: kube-scheduler
         CidrIp: 10.5.0.0/16
       - IpProtocol: tcp
         FromPort: 10252
         ToPort: 10252
         Description: kube-controller-manager
         CidrIp: 10.5.0.0/16
         # k8s pod NW
       - IpProtocol: tcp
         FromPort: 0
         ToPort: 65535
         Description: kube-controller-manager
         CidrIp: 192.168.0.0/16

 # k8s worker node用SG
 EC2WorkerSG:
   Type: AWS::EC2::SecurityGroup
   Properties:
     GroupName: ec2-worker-sg-cf
     GroupDescription: Allow SSH and HTTP and kubernetes worker access
     VpcId: !Ref VPC
     SecurityGroupIngress:
       # ssh
       - IpProtocol: tcp
         FromPort: 22
         ToPort: 22
         CidrIp: !Ref MyIP
       # k8s
       - IpProtocol: tcp
         FromPort: 10250
         ToPort: 10250
         Description: kubelet
         CidrIp: 10.5.0.0/16
       - IpProtocol: tcp
         FromPort: 30000
         ToPort: 32767
         Description: NodePort Services
         CidrIp: 10.5.0.0/16
       - IpProtocol: tcp
         FromPort: 10250
         ToPort: 10250
         Description: kubelet
         CidrIp: 192.168.0.0/16
       - IpProtocol: tcp
         FromPort: 30000
         ToPort: 32767
         Description: NodePort Services
         CidrIp: 192.168.0.0/16
       # 動作確認用node port
       - IpProtocol: tcp
         FromPort: 30000
         ToPort: 32767
         CidrIp: !Ref MyIP

マスターノードおよびワーカーノードで利用するk8s用ポートおよびSSHポートの通信を許可するSGを作成。

通信を許可するIPアドレスにはEC2ホストのアドレス帯10.5.0.0/16およびk8sのPod NWのアドレス帯192.168.0.0/16を指定しています。

また、構築後の動作確認において、ワーカーノードのNodePort(30000~32767)に向けてラップトップからアクセスするため、その通信を許可しています。

k8sがどのポートを利用するかは公式ドキュメントに記載されています。


  EC2IAMRole: 
   Type: AWS::IAM::Role
   Properties: 
     RoleName: ec2-role-cf
     AssumeRolePolicyDocument: 
       Version: "2012-10-17"
       Statement: 
         - Effect: Allow
           Principal: 
             Service: 
               - "ec2.amazonaws.com"
           Action: 
             - "sts:AssumeRole"
     Path: "/"
     ManagedPolicyArns: 
       # 検証用なのでAdmin権限付与
       - "arn:aws:iam::aws:policy/AdministratorAccess"

 EC2InstanceProfile: 
   Type: AWS::IAM::InstanceProfile
   Properties: 
     Path: "/"
     Roles: 
       - Ref: EC2IAMRole
     InstanceProfileName: ec2-instance-profile-cf

最後は、EC2にアタッチするIAM Roleの設定です。今回は検証用なのでAdmin権限を付与しています。以上でCloudformationテンプレートファイルの説明を終わります。


Cloudfromation実行

ラップトップからAWS CLIコマンドを利用してCloudformation stackを作成する場合のコマンドは以下になります。

aws cloudformation create-stack \
--stack-name cf-k8s \
--region ap-northeast-1 \
--template-body file://cf-k8s.yml \
--parameters ParameterKey=KeyName,ParameterValue=[your key name] \
ParameterKey=MyIP,ParameterValue=[XXX.XXX.XXX.XXX/32] \
--capabilities CAPABILITY_NAMED_IAM

--stack-nameはcf-k8sとしていますが、任意の名前を指定してください。

--template-bodyで読み込むテンプレートファイルを指定しています。今回はローカル環境にcf-k8s.ymlという名前のテンプレートファイルを作成しましたので、上記の引数を指定しています。

[your key name]にはEC2作成時に利用するキーペア名を指定してください。

[XXX.XXX.XXX.XXX/32]にはEC2へのSSH元IPアドレスを指定してください。

--capabilities CAPABILITY_NAMED_IAMについては、IAMをテンプレート内で作成する場合に必要なパラメータとなります。


k8sクラスタ構築

Cloudformationのstack作成に成功し、必要なリソースが作成されたら、EC2にSSHでログインして、k8sクラスタの構築を行います。実行コマンドはgithubのREADMEにまとめてますが、細かい説明は以下を参考にしていただければと思います。


k8sクラスタ構築後の動作確認

無事にクラスタ構築が完了しましたら、実際にサンプルpodとserviceをデプロイしてそのpodにアクセスできるか試してみましょう。私が確認した手順は以下を参考にしました。

手順はgithubのREADMEに記載していますので、とりあえずサンプルpodを動かしてみたいという方は参考にしてみてください。



以上で今回Cloudformationおよびk8sマルチクラスタ構築方法を学ぶためにやってみた手順の紹介を終わります。こうやって動かしてみて、k8sのCNIやdockerのnetwork周りの理解が不足していることが分かりました。Cloudformationを使えば手軽にVM環境をつくれるので、引き続きnetwork周りの状態確認や検証を行っていきたいと思います。

よろしければサポートお願いします!頂いたサポート費は、執筆活動に使わせて頂きます。