k8sマルチマスター をEC2 by Cloudformationでやってみた
仕事でk8sに触れる機会はあったものの、マスターノードはフルマネージドサービスを利用するのが当たり前なため、どのような仕組みでマスターノードが動いているのかほとんど分からないまま使っていました。そこで、少しでも理解を深めるため、kubeadmを使ってVMノード(EC2)上にk8sマルチマスタを構築してみました。
また、せっかくAWSを利用するならこれまで触る機会が少なかったCloudformationを使ってインフラ周りを構築しようということで、簡単なテンプレートを作成しました。これからCloudformationを学習しようとしている方に少しでも参考になれば幸いです。
今回はやってみた記事なのですが、Cloudformationを使ってインフラ構築した部分以外は以下の記事をほぼほぼ参考にさせていただいてますので、k8sクラスタ構築に関わる詳細は以下を参考にしていただければと思います。
全体構成
・ハードウェア構成:
- 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周りの状態確認や検証を行っていきたいと思います。
よろしければサポートお願いします!頂いたサポート費は、執筆活動に使わせて頂きます。