NECネッツエスアイ

「Kubernetes The Hard Way」をAWSでトライしてみた

目次

  1. はじめに
  2. 参考文献
  3. 構築する環境
  4. 事前準備
  5. クライアントツールのインストール
  6. EC2でComputing Resourceをプロビジョニング
  7. 認証局(CA)のプロビジョニングとTLS証明書の生成
  8. 認証用kubeconfigの生成
  9. 暗号化の設定とキーの生成
  10. etcdの起動
  11. Kubernetesコントロールプレーンの起動
  12. ワーカーノードの起動
  13. リモートアクセス用のkubectl設定
  14. クラスタ内ネットワークの設定
  15. DNSクラスタアドオンの導入
  16. スモークテスト
  17. crictlを使用してワーカーノードのイメージ・ポッド・コンテナをチェックする
  18. 後片付け
  19. おわりに

はじめに

事業開発推進部の武井です。

「Kubernetes The Hard Way」をご存知でしょうか。
Kelsey Hightowerさんによって執筆された、Kubernetesを1から手作業で構築する手順がまとめられたチュートリアルです。
オリジナルのチュートリアルではGCPを使った構築手順が記載されていますが、私が普段使用する頻度の高いAWSでの再現を試みてみました。

参考文献

AWS環境に本チュートリアルを構成するにあたり、以下の記事を参考にしています。

https://github.com/prabhatsharma/kubernetes-the-hard-way-aws/blob/master/README.md

https://qiita.com/rabiho/items/1e7423909701a44a69af

構築する環境

Kubernetes The Hard Wayでは、コンポーネント間の暗号化とRBAC認証ができる、可用性の高いクラスタを構築します。

使用するコンポーネントとそのバージョン一覧は下記の通りです。

  • Kubernetes v1.24.0
  • containerd v1.16.6
  • coredns v1.19.3
  • cni v1.1.1
  • etcd v3.5.4

01-事前準備

Amazon Web Service

このチュートリアルではAmazon Web Service(以下AWS)を利用して、kubernetesクラスターを立ち上げます。今回使用するリソースはAWSの無料枠を超えるので、チュートリアル終了後には作成したリソースをクリーンアップし、不要なコストが発生しないよう注意してください。

AWS CLIのインストール

AWS CLIをインストールし、必要な設定を行います。 詳細手順に関してはAWSの公式ドキュメントを参照ください。
インストール完了後、以下のコマンドでAWS CLIが有効であることを確認します。

デフォルトリージョンの設定

このチュートリアルで使用するデフォルトリージョンを設定します。

AWS_REGION=ap-northeast-1aws configure set default.region $AWS_REGION

tmuxを使ったパラレルなコマンド実行

オリジナルのチュートリアルでも推奨の設定として記載されています。
この手順をスキップしてもKubernetesクラスタの動作に影響はありません。

kubernetes-the-hard-way/01-prerequisites.md at master · kelseyhightower/kubernetes-the-hard-way Bootstrap Kubernetes the hard way on Google Cloud Platform. N github.com

作業用ディレクトリの作成

本チュートリアルでは、多くのファイル生成や転送の処理を行います。
また、リモート接続のためのキーペアの払い出しも行います。
ファイル管理の観点から、予め作業用ディレクトリを作成し、そこをチュートリアル中のカレントディレクトリとすることを推奨します。

02-クライアントツールのインストール

このセクションではcfssl, cfssljson,kubectlのインストールを行います。

CFSSLとCFSSLJSONのインストール

cvfsslとcfssljsonはPKI環境の構築とTLSの証明書発行に使用するツールです。

OS X(Mac OS)

curl -o cfssl https://pkg.cfssl.org/R1.2/cfssl_darwin-amd64
curl -o cfssljson https://pkg.cfssl.org/R1.2/cfssljson_darwin-amd64
chmod +x cfssl cfssljson
sudo mv cfssl cfssljson /usr/local/bin/

OS Xを使用しpre-buildでインストールしようとした場合、問題が生じる場合があります
その場合はHomebrewを使用してインストールを行なってください。
インストールのコマンドは以下です。

brew install cfssl

Linux

wget -q --show-progress --https-only --timestamping \
  https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 \
  https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64

chmod +x cfssl_linux-amd64 cfssljson_linux-amd64
chmod +x cfssl_linux-amd64 cfssljson_linux-amd64
sudo mv cfssljson_linux-amd64 /usr/local/bin/cfssljson

確認

cfssl version

動作確認済みのバージョンは以下です。

Version: 1.6.1
Runtime: go1.17.2

kubectlのインストール

kubectlはKubernetesが持つAPI Serverとの通信に使用されるコマンドラインツールです。今回はv1.24.0を使用しています。

OS X

curl -o kubectl https://storage.googleapis.com/kubernetes-release/release/v1.24.0/bin/darwin/amd64/kubectl
chmod +x kubectl
sudo mv kubectl /usr/local/bin/

Linux

wget https://storage.googleapis.com/kubernetes-release/release/v1.24.0/bin/linux/amd64/kubectl
chmod +x kubectl
sudo mv kubectl /usr/local/bin/

確認

kubectlバージョン1.24.0がインストールされていることを確認します。

kubectl version --client

出力例

Client Version: version.Info{Major:"1", Minor:"24", GitVersion:"v1.24.0", GitCommit:"4ce5a8954017644c5420bae81d72b09b735c21f0", GitTreeState:"clean", BuildDate:"2022-05-03T13:46:05Z", GoVersion:"go1.18.1", Compiler:"gc", Platform:"darwin/amd64"}Kustomize Version: v4.5.4

03-EC2でComputing Resourceをプロビジョニング

Kubernetesはクラスタを管理するためのControll Planeと、コンテナを実行するためのKubernetes Nodeで構成されており、それらをホストするためのこのユーティングリソースが必要です。
このセクションでは、単一のコンピューティングゾーンで安全で可用性の高いKubernetesクラスターを実行するために必要なコンピューティングリソースをプロビジョニングします。

ネットワーク

Kubernetesのネットワークモデルは、コンテナとノードが相互に通信できるフラットネットワークを想定しています。これが正しく構成されていない場合、ネットワークポリシーによりコンテナ間や外部ネットワークへの通信が制限される可能性があります。尚、Network Policyの設定はこのチュートリアルの範囲外です。

仮想プライベートクラウド(VPC)

このステップでは、Kubernetesクラスタをホストするために専用の仮想プライベートクラウド(VPC)やネットワークをセットアップします。
今回はkubernetes-the-hard-wayというValueを持ったVPCを作成します。

VPCの作成

VPC_ID=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 --output text --query 'Vpc.VpcId')
aws ec2 create-tags --resources ${VPC_ID} --tags Key=Name,Value=kubernetes-the-hard-way
aws ec2 modify-vpc-attribute --vpc-id ${VPC_ID} --enable-dns-support '{"Value": true}'
aws ec2 modify-vpc-attribute --vpc-id ${VPC_ID} --enable-dns-hostnames '{"Value": true}'

Subnetの作成

サブネットは、Kubernetesクラスタ内の各ノードにプライベートIPアドレスを割り当てるためのリソースです。IPレンジに関しては十分な範囲を持たせてください。尚、このチュートリアルでは10.0.1.0/24のサブネットを使用しており、最大254のコンピューティングインスタンスをホストできます。
以下はkubernetes-the-hard-wayのVPCにkubernetesサブネットをプロビジョニングしています。

SUBNET_ID=$(aws ec2 create-subnet \
  --vpc-id ${VPC_ID} \
  --cidr-block 10.0.1.0/24 \
  --output text --query 'Subnet.SubnetId')
aws ec2 create-tags --resources ${SUBNET_ID} --tags Key=Name,Value=kubernetes

Internet Gatewayの作成

インターネットゲートウェイはKubernetesのコントローラが外部からAPIのエンドポイントとなるために必要な構成です。
以下はkubernetes-the-hard-wayのVPCにkubernetes Internet Gatewayを作成しています。

INTERNET_GATEWAY_ID=$(aws ec2 create-internet-gateway --output text --query 'InternetGateway.InternetGatewayId')
aws ec2 create-tags --resources ${INTERNET_GATEWAY_ID} --tags Key=Name,Value=kubernetes
aws ec2 attach-internet-gateway --internet-gateway-id ${INTERNET_GATEWAY_ID} --vpc-id ${VPC_ID}

Route Tablesの作成

ルートテーブルは、VPC内の仮想ルータが参照するルーティングテーブルです。以下はkubernetes-the-hard-wayのVPCに0.0.0.0/0のデフォルトルートを設定しています。

ROUTE_TABLE_ID=$(aws ec2 create-route-table --vpc-id ${VPC_ID} --output text --query 'RouteTable.RouteTableId')
aws ec2 create-tags --resources ${ROUTE_TABLE_ID} --tags Key=Name,Value=kubernetes
aws ec2 associate-route-table --route-table-id ${ROUTE_TABLE_ID} --subnet-id ${SUBNET_ID}
aws ec2 create-route --route-table-id ${ROUTE_TABLE_ID} --destination-cidr-block 0.0.0.0/0 --gateway-id ${INTERNET_GATEWAY_ID}

Security Groupsの作成

セキュリティグループはVPC内の仮想ファイアウォールとして機能します。
以下はKubernetesクラスタ間の内部通信と外部SSH,ICMPおよびHTTPSを許可するセキュリティポリシーを記載しています。

SECURITY_GROUP_ID=$(aws ec2 create-security-group \
  --group-name kubernetes \
  --description "Kubernetes security group" \
  --vpc-id ${VPC_ID} \
  --output text --query 'GroupId')
aws ec2 create-tags --resources ${SECURITY_GROUP_ID} --tags Key=Name,Value=kubernetes
aws ec2 authorize-security-group-ingress --group-id ${SECURITY_GROUP_ID} --protocol all --cidr 10.0.0.0/16
aws ec2 authorize-security-group-ingress --group-id ${SECURITY_GROUP_ID} --protocol all --cidr 10.200.0.0/16
aws ec2 authorize-security-group-ingress --group-id ${SECURITY_GROUP_ID} --protocol tcp --port 22 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --group-id ${SECURITY_GROUP_ID} --protocol tcp --port 6443 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --group-id ${SECURITY_GROUP_ID} --protocol tcp --port 443 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --group-id ${SECURITY_GROUP_ID} --protocol icmp --port -1 --cidr 0.0.0.0/0

Kubernetes Public Accessの設定

Kubernetes APIサーバのエンドポイントとして割り当てるIPアドレスを設定します。

LOAD_BALANCER_ARN=$(aws elbv2 create-load-balancer \
    --name kubernetes \
    --subnets ${SUBNET_ID} \
    --scheme internet-facing \
    --type network \
    --output text --query 'LoadBalancers[].LoadBalancerArn')
  TARGET_GROUP_ARN=$(aws elbv2 create-target-group \
    --name kubernetes \
    --protocol TCP \
    --port 6443 \
    --vpc-id ${VPC_ID} \
    --target-type ip \
    --output text --query 'TargetGroups[].TargetGroupArn')
  aws elbv2 register-targets --target-group-arn ${TARGET_GROUP_ARN} --targets Id=10.0.1.1{0,1,2}
  aws elbv2 create-listener \
    --load-balancer-arn ${LOAD_BALANCER_ARN} \
    --protocol TCP \
    --port 443 \
    --default-actions Type=forward,TargetGroupArn=${TARGET_GROUP_ARN} \
    --output text --query 'Listeners[].ListenerArn'

インスタンス

このステップでは、KubernetesクラスタのホストとなるVMをプロビジョニングします。

Instance Imageの定義

利用するインスタンスイメージを定義します。
今回はパブリックとして公開しているubuntuのAMIを指定しています。

IMAGE_ID=$(aws ec2 describe-images --owners 099720109477 \
  --output json \
  --filters \
  'Name=root-device-type,Values=ebs' \
  'Name=architecture,Values=x86_64' \
  'Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*' \
  | jq -r '.Images|sort_by(.Name)[-1]|.ImageId')

jqコマンドが入っていない場合は以下のコマンドでインストールを行います。

brew install jq

SSH Key Pairの作成

EC2インスタンスにSSH接続するためのKey Pairを作成します。

aws ec2 create-key-pair --key-name kubernetes --output text --query 'KeyMaterial' > kubernetes.id_rsa
chmod 600 kubernetes.id_rsa

Kubernetes Control Plane用ホストの立ち上げ

Amazon EC2で、Kubernetesコントローラノードのホストとなる仮想マシンを作成します。今回は t3.microインスタンスを使用し、3台の仮想マシンを立ち上げます。

for i in 0 1 2; do
  instance_id=$(aws ec2 run-instances \
    --associate-public-ip-address \
    --image-id ${IMAGE_ID} \
    --count 1 \
    --key-name kubernetes \
    --security-group-ids ${SECURITY_GROUP_ID} \
    --instance-type t3.micro \
    --private-ip-address 10.0.1.1${i} \
    --user-data "name=controller-${i}" \
    --subnet-id ${SUBNET_ID} \
    --block-device-mappings='{"DeviceName": "/dev/sda1", "Ebs": { "VolumeSize": 50 }, "NoDevice": "" }' \
    --output text --query 'Instances[].InstanceId')
  aws ec2 modify-instance-attribute --instance-id ${instance_id} --no-source-dest-check
  aws ec2 create-tags --resources ${instance_id} --tags "Key=Name,Value=controller-${i}"
  echo "controller-${i} created "
done

Kubernetes Worker Node用ホストの立ち上げ

Amazon EC2でKubernetes Worker Nodeのホストとなる仮想マシンを作成します。今回は t3.microインスタンスを使用し、3台の仮想マシンを立ち上げます。
Kubernetes Worker Nodeはコンテナアプリケーションが展開、実行される場所であるため実際のサイジングは非常に重要です。アプリケーションの要件に応じたリソース計算が求められます。

for i in 0 1 2; do
  instance_id=$(aws ec2 run-instances \
    --associate-public-ip-address \
    --image-id ${IMAGE_ID} \
    --count 1 \
    --key-name kubernetes \
    --security-group-ids ${SECURITY_GROUP_ID} \
    --instance-type t3.micro \
    --private-ip-address 10.0.1.2${i} \
    --user-data "name=worker-${i}|pod-cidr=10.200.${i}.0/24" \
    --subnet-id ${SUBNET_ID} \
    --block-device-mappings='{"DeviceName": "/dev/sda1", "Ebs": { "VolumeSize": 50 }, "NoDevice": "" }' \
    --output text --query 'Instances[].InstanceId')
  aws ec2 modify-instance-attribute --instance-id ${instance_id} --no-source-dest-check
  aws ec2 create-tags --resources ${instance_id} --tags "Key=Name,Value=worker-${i}"
  echo "worker-${i} created"
done

04-認証局(CA)のプロビジョニングとTLS証明書の生成

このセクションでは、CloudFlareのPKIツールキットである cfsslを使用してPKI基盤をプロビジョニングします。PKI基盤を利用して認証局を作り、以下を生成します。

  • admin用のTLS証明書
  • 各コンポーネントのTLS証明書
    • etcd
    • kube-apiserver
    • kube-controller-manager
    • kube-scheduler
    • kubelet
    • kube-proxy

各コンポーネントの役割は以下を参照ください。

Kubernetes ComponentsKubernetes Components A Kubernetes cluster consists of the components that represen kubernetes.io

認証局(CA)の立ち上げ

このステップでは、TLS証明書を生成するための認証局(CA)をプロビジョニングします。
CA設定ファイル、CA自身の証明書、および秘密鍵を生成します。

cat > ca-config.json <<EOF
{
  "signing": {
    "default": {
      "expiry": "8760h"
    },
    "profiles": {
      "kubernetes": {
        "usages": ["signing", "key encipherment", "server auth", "client auth"],
        "expiry": "8760h"
      }
    }
  }
}
EOF

cat > ca-csr.json <<EOF
{
  "CN": "Kubernetes",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "Kubernetes",
      "OU": "CA",
      "ST": "Oregon"
    }
  ]
}
EOF

cfssl gencert -initca ca-csr.json | cfssljson -bare ca

以下のファイルが生成されます。

ca-key.pem
ca.pem

クライアントとサーバーの証明書発行

このステップでは、各Kubernetesコンポーネントで使うクライアント証明書とサーバー証明書、adminユーザー用のクライアント証明書を発行します。

Admin用クライアント証明書の発行

admin用のクライアント証明書と秘密鍵を生成します。

cat > admin-csr.json <<EOF
{
  "CN": "admin",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:masters",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  admin-csr.json | cfssljson -bare admin

以下のファイルが生成されます。

admin-key.pem
admin.pem

Kubelet用クライアント証明書の発行

Kubernetesは、Node Authorizerと呼ばれる特別な用途向けの認可モードを利用します。主にKubeletsからのAPIリクエストの認証の役割を担いますが、Node Authorizerの認可のためにはsystem:nodes groupの中のsystem:node:<nodeName>というユーザー名で認証されるように証明書を作成しなければなりません。詳細は以下をご覧ください。

Using Node AuthorizationUsing Node Authorization Node authorization is a special-purpose authorization mode th kubernetes.io

このステップでは、KubernetesワーカーノードごとにNode Authorizerの要求を満たす証明書と秘密鍵を発行します。

for i in 0 1 2; do
  instance="worker-${i}"
  instance_hostname="ip-10-0-1-2${i}"
  cat > ${instance}-csr.json <<EOF
{
  "CN": "system:node:${instance_hostname}",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:nodes",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

  external_ip=$(aws ec2 describe-instances --filters \
    "Name=tag:Name,Values=${instance}" \
    "Name=instance-state-name,Values=running" \
    --output text --query 'Reservations[].Instances[].PublicIpAddress')

  internal_ip=$(aws ec2 describe-instances --filters \
    "Name=tag:Name,Values=${instance}" \
    "Name=instance-state-name,Values=running" \
    --output text --query 'Reservations[].Instances[].PrivateIpAddress')

  cfssl gencert \
    -ca=ca.pem \
    -ca-key=ca-key.pem \
    -config=ca-config.json \
    -hostname=${instance_hostname},${external_ip},${internal_ip} \
    -profile=kubernetes \
    worker-${i}-csr.json | cfssljson -bare worker-${i}
done

以下のファイルが生成されます。


worker-0-key.pem
worker-0.pem
worker-1-key.pem
worker-1.pem
worker-2-key.pem
worker-2.pem

kube-contorller-manager用 クライアント証明書の発行

kube-controller-managerのクライアント証明書と秘密鍵を発行します。

cat > kube-controller-manager-csr.json <<EOF
{
  "CN": "system:kube-controller-manager",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:kube-controller-manager",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  kube-controller-manager-csr.json | cfssljson -bare kube-controller-manager

以下のファイルが生成されます。

kube-controller-manager-key.pem
kube-controller-manager.pem

kube-proxy 用 クライアント証明書の発行

kube-proxyのクライアント証明書と秘密鍵を発行します。

cat > kube-proxy-csr.json <<EOF
{
  "CN": "system:kube-proxy",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:node-proxier",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  kube-proxy-csr.json | cfssljson -bare kube-proxy

以下のファイルが生成されます。

kube-proxy-key.pem
kube-proxy.pem

kube-scheduler用 クライアント証明書の発行

kube-scheduler クライアント用の証明書と秘密鍵を発行します。

cat > kube-scheduler-csr.json <<EOF
{
  "CN": "system:kube-scheduler",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "system:kube-scheduler",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  kube-scheduler-csr.json | cfssljson -bare kube-scheduler

以下のファイルが生成されます。

kube-scheduler-key.pem
kube-scheduler.pem

Kubernetes API サーバー用証明書の発行

kubernetes-the-hard-way のstatic IPアドレスは、Kubernetes API サーバーの証明書のSAN(subject alternative names)のリストに含める必要があります。
外部のクライアントでも証明書を使った検証を行うための設定です。

Kubernetes API サーバーの証明書と秘密鍵を生成します。

KUBERNETES_HOSTNAMES=kubernetes,kubernetes.default,kubernetes.default.svc,kubernetes.default.svc.cluster,kubernetes.svc.cluster.local

cat > kubernetes-csr.json <<EOF
{
  "CN": "kubernetes",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "Kubernetes",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -hostname=10.32.0.1,10.0.1.10,10.0.1.11,10.0.1.12,${KUBERNETES_PUBLIC_ADDRESS},127.0.0.1,${KUBERNETES_HOSTNAMES} \
  -profile=kubernetes \
  kubernetes-csr.json | cfssljson -bare kubernetes

以下のファイルが生成されます。

kubernetes-key.pem
kubernetes.pem

サービスアカウントのキーペアを発行

サービスアカウントの管理のドキュメントにあるように、Kubernetes Controller Managerは、サービスアカウントのトークンの生成と署名をするためにキーペアを使用します。

service-account の証明書と秘密鍵を発行します。

cat > service-account-csr.json <<EOF
{
  "CN": "service-accounts",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "US",
      "L": "Portland",
      "O": "Kubernetes",
      "OU": "Kubernetes The Hard Way",
      "ST": "Oregon"
    }
  ]
}
EOF

cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes \
  service-account-csr.json | cfssljson -bare service-account

以下のファイルが生成されます。

service-account-key.pem
service-account.pem

クライアント証明書とサーバー証明書の配布

証明書と秘密鍵をコピーし、各ワーカーインスタンスに配置します。
配置対象は以下です。

  • CA証明書
  • APIサーバの証明書
  • ワーカーノードの証明書と秘密鍵
for instance in worker-0 worker-1 worker-2; do
  external_ip=$(aws ec2 describe-instances --filters \
    "Name=tag:Name,Values=${instance}" \
    "Name=instance-state-name,Values=running" \
    --output text --query 'Reservations[].Instances[].PublicIpAddress')

  scp -i kubernetes.id_rsa ca.pem ${instance}-key.pem ${instance}.pem ubuntu@${external_ip}:~/
done

接続処理を続けて良いか聞かれた場合はyesを入力します。

Are you sure you want to continue connecting (yes/no/[fingerprint])?

転送に成功すると以下のような出力結果を得られます。

ca.pem                                        100% 1318    32.0KB/s   00:00    
worker-1-key.pem                              100% 1675    72.6KB/s   00:00    
worker-1.pem                                  100% 1509    40.6KB/s   00:00 

上記はワーカーインスタンスの台数分繰り返し処理されます。コントローラーインスタンスにも同様に配置します。
配置対象は以下です。

  • CA証明書
  • APIサーバの証明書と秘密鍵
  • サービスアカウント払い出し用キーペア
for instance in controller-0 controller-1 controller-2; do
  external_ip=$(aws ec2 describe-instances --filters \
    "Name=tag:Name,Values=${instance}" \
    "Name=instance-state-name,Values=running" \
    --output text --query 'Reservations[].Instances[].PublicIpAddress')

  scp -i kubernetes.id_rsa \
    ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem \
    service-account-key.pem service-account.pem ubuntu@${external_ip}:~/
done

接続処理を続けて良いか聞かれた場合は’yes’を入力します。

Are you sure you want to continue connecting (yes/no/[fingerprint])?

転送に成功すると以下のような出力結果を得られます。

ca.pem                                        100% 1318    59.2KB/s   00:00    
ca-key.pem                                    100% 1679    42.0KB/s   00:00    
kubernetes-key.pem                            100% 1675    73.4KB/s   00:00    
kubernetes.pem                                100% 1598    44.6KB/s   00:00    
service-account-key.pem                       100% 1675    72.0KB/s   00:00    
service-account.pem                           100% 1440    65.8KB/s   00:00  

尚、下記コンポーネントのクライアント証明書は次の手順で使用します。

  • kube-proxy
  • kube-controller-manager
  • kube-scheduler
  • kubelet

05-認証用kubeconfigの生成

このセクションでは、Kubernetes APIサーバーがKubernetesのクライアントを配置、認証できるようにするためのkubeconfig(Kubernetes構成ファイル)を生成します。生成するファイルは以下です。

  • conttoller-manager
  • kubelet
  • kube-proxy
  • scheduler
  • adminユーザー用のkubeconfig

KubernetesのPublic DNSアドレスを取得

kubeconfigはKubernetes APIサーバと接続できることが要求されます。
高可用性を実現するために、Kubernetes APIサーバの前に設置されている外部ロードバランサーのIPアドレスを使用します。

kubernetes-the-hard-wayのDNSアドレスを取得します。

KUBERNETES_PUBLIC_ADDRESS=$(aws elbv2 describe-load-balancers \
  --load-balancer-arns ${LOAD_BALANCER_ARN} \
  --output text --query 'LoadBalancers[0].DNSName')
echo ${KUBERNETES_PUBLIC_ADDRESS}

kubelet用kubeconfigsの生成

kubelet用のkubeconfigファイルを生成するときは、kubeletのノード名と同じクライアント証明書を使用する必要があります。これにより、kubeletがKubernetesのNode Authorizerによって認可されるようになります。

ワーカーノード毎にkubeconfigを生成します。

for instance in worker-0 worker-1 worker-2; do
  kubectl config set-cluster kubernetes-the-hard-way \
    --certificate-authority=ca.pem \
    --embed-certs=true \
    --server=https://${KUBERNETES_PUBLIC_ADDRESS}:443 \
    --kubeconfig=${instance}.kubeconfig

  kubectl config set-credentials system:node:${instance} \
    --client-certificate=${instance}.pem \
    --client-key=${instance}-key.pem \
    --embed-certs=true \
    --kubeconfig=${instance}.kubeconfig

  kubectl config set-context default \
    --cluster=kubernetes-the-hard-way \
    --user=system:node:${instance} \
    --kubeconfig=${instance}.kubeconfig

  kubectl config use-context default --kubeconfig=${instance}.kubeconfig
done

以下のファイルが生成されます。

worker-0.kubeconfig
worker-1.kubeconfig
worker-2.kubeconfig

kube-proxy用kubeconfigの生成

kube-proxyのkubeconfigも生成します。

kubectl config set-cluster kubernetes-the-hard-way \
  --certificate-authority=ca.pem \
  --embed-certs=true \
  --server=https://${KUBERNETES_PUBLIC_ADDRESS}:443 \
  --kubeconfig=kube-proxy.kubeconfig

kubectl config set-credentials system:kube-proxy \
  --client-certificate=kube-proxy.pem \
  --client-key=kube-proxy-key.pem \
  --embed-certs=true \
  --kubeconfig=kube-proxy.kubeconfig

kubectl config set-context default \
  --cluster=kubernetes-the-hard-way \
  --user=system:kube-proxy \
  --kubeconfig=kube-proxy.kubeconfig

kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig

以下のファイルが生成されます。

kube-proxy.kubeconfig

kube-controller-manager用kubeconfigの生成

kube-controller-managerのkubeconfigを生成します。

kubectl config set-cluster kubernetes-the-hard-way \
  --certificate-authority=ca.pem \
  --embed-certs=true \
  --server=https://127.0.0.1:6443 \
  --kubeconfig=kube-controller-manager.kubeconfig

kubectl config set-credentials system:kube-controller-manager \
  --client-certificate=kube-controller-manager.pem \
  --client-key=kube-controller-manager-key.pem \
  --embed-certs=true \
  --kubeconfig=kube-controller-manager.kubeconfig

kubectl config set-context default \
  --cluster=kubernetes-the-hard-way \
  --user=system:kube-controller-manager \
  --kubeconfig=kube-controller-manager.kubeconfig

kubectl config use-context default --kubeconfig=kube-controller-manager.kubeconfig

以下のファイルが生成されます。

kube-controller-manager.kubeconfig

kube-scheduler用kubeconfigの生成

kube-schedulerのkubeconfigを生成します。

kubectl config set-cluster kubernetes-the-hard-way \
  --certificate-authority=ca.pem \
  --embed-certs=true \
  --server=https://127.0.0.1:6443 \
  --kubeconfig=kube-scheduler.kubeconfig

kubectl config set-credentials system:kube-scheduler \
  --client-certificate=kube-scheduler.pem \
  --client-key=kube-scheduler-key.pem \
  --embed-certs=true \
  --kubeconfig=kube-scheduler.kubeconfig

kubectl config set-context default \
  --cluster=kubernetes-the-hard-way \
  --user=system:kube-scheduler \
  --kubeconfig=kube-scheduler.kubeconfig

kubectl config use-context default --kubeconfig=kube-scheduler.kubeconfig

以下のファイルが生成されます。

kube-scheduler.kubeconfig

adminユーザー用kubeconfigの生成

adminユーザーのkubeconfigを生成します。

kubectl config set-cluster kubernetes-the-hard-way \
  --certificate-authority=ca.pem \
  --embed-certs=true \
  --server=https://127.0.0.1:6443 \
  --kubeconfig=admin.kubeconfig

kubectl config set-credentials admin \
  --client-certificate=admin.pem \
  --client-key=admin-key.pem \
  --embed-certs=true \
  --kubeconfig=admin.kubeconfig

kubectl config set-context default \
  --cluster=kubernetes-the-hard-way \
  --user=admin \
  --kubeconfig=admin.kubeconfig

kubectl config use-context default --kubeconfig=admin.kubeconfig

以下のファイルが生成されます。

admin.kubeconfig

kubeconfigの配布

kubeletとkube-proxyのkubecnofigをコピーし、各ワーカーノードに配置します。

for instance in worker-0 worker-1 worker-2; do
  external_ip=$(aws ec2 describe-instances --filters \
    "Name=tag:Name,Values=${instance}" \
    "Name=instance-state-name,Values=running" \
    --output text --query 'Reservations[].Instances[].PublicIpAddress')

  scp -i kubernetes.id_rsa \
    ${instance}.kubeconfig kube-proxy.kubeconfig ubuntu@${external_ip}:~/
done

出力例

worker-0.kubeconfig                           100% 6449   260.8KB/s   00:00    
kube-proxy.kubeconfig                         100% 6371   261.1KB/s   00:00    
worker-1.kubeconfig                           100% 6449   273.1KB/s   00:00    
kube-proxy.kubeconfig                         100% 6371   268.2KB/s   00:00    
worker-2.kubeconfig                           100% 6449   265.6KB/s   00:00    
kube-proxy.kubeconfig                         100% 6371   287.2KB/s   00:00 

kube-controller-managerkube-schedulerkubeconfigをコピーし、各コントローラーノードに配置します。

for instance in controller-0 controller-1 controller-2; do
  external_ip=$(aws ec2 describe-instances \
    --filters "Name=tag:Name,Values=${instance}" \
    --output text --query 'Reservations[].Instances[].PublicIpAddress')

  scp -i kubernetes.id_rsa \
    admin.kubeconfig kube-controller-manager.kubeconfig kube-scheduler.kubeconfig ubuntu@${external_ip}:~/
done

出力例

admin.kubeconfig                              100% 6265   255.2KB/s   00:00    
kube-controller-manager.kubeconfig            100% 6387    51.6KB/s   00:00    
kube-scheduler.kubeconfig                     100% 6241   249.1KB/s   00:00    
admin.kubeconfig                              100% 6265   276.1KB/s   00:00    
kube-controller-manager.kubeconfig            100% 6387   271.4KB/s   00:00    
kube-scheduler.kubeconfig                     100% 6241   122.2KB/s   00:00    
admin.kubeconfig                              100% 6265   278.5KB/s   00:00    
kube-controller-manager.kubeconfig            100% 6387   127.7KB/s   00:00    
kube-scheduler.kubeconfig                     100% 6241   276.7KB/s   00:00

06-暗号化の設定とキーの生成

Kubernetesは、クラスタの状態、アプリケーションの設定、秘匿情報などを含むさまざまなデータが格納されます。そのデータを守るために、Kubernetesはクラスタ内で保持しているデータを暗号化する機能が提供されています。このステップでは、Kubernetes Secretsの暗号化に合わせた暗号化鍵と暗号化の設定ファイルを生成します。

暗号化鍵

暗号化に使用する鍵を作成します。

ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)

暗号化の設定ファイル

暗号化の設定のためのencryption-config.yamlを生成します。

cat > encryption-config.yaml <<EOF
kind: EncryptionConfig
apiVersion: v1
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: ${ENCRYPTION_KEY}
      - identity: {}
EOF

encryption-config.yamlをコピーし、各コントローラーノードに配置します。

for instance in controller-0 controller-1 controller-2; do
  external_ip=$(aws ec2 describe-instances --filters \
    "Name=tag:Name,Values=${instance}" \
    "Name=instance-state-name,Values=running" \
    --output text --query 'Reservations[].Instances[].PublicIpAddress')
  
  scp -i kubernetes.id_rsa encryption-config.yaml ubuntu@${external_ip}:~/
done

出力例

encryption-config.yaml                        100%  240    10.4KB/s   00:00    
encryption-config.yaml                        100%  240     5.2KB/s   00:00    
encryption-config.yaml                        100%  240     4.9KB/s   00:00 

07-etcdの起動

Kubernetesの各コンポーネントはステートレスになっており、クラスタの状態は全てetcdに格納され管理されています。そのため、etcdはKubernetesにおいて重要なコンポーネントであると位置付けられており、多くの場合はetcdの高い可用性を担保することが求められます。このセクションでは、3ノードのetcdクラスタを構築して、高可用性と安全な外部アクセスを実現します。

準備

このステップのコマンドは、controller-0controller-1controller-2の各コントローラインスタンスで実行する必要があります。以下の様にsshコマンドを使用して各コントローラーノードにログインしてください。

for instance in controller-0 controller-1 controller-2; do
  external_ip=$(aws ec2 describe-instances \
    --filters "Name=tag:Name,Values=${instance}" \
    --output text --query 'Reservations[].Instances[].PublicIpAddress')

  echo ssh -i kubernetes.id_rsa ubuntu@$external_ip
done

ここからの手順は、直前のコマンドによって出力されたそれぞれのIPアドレスにssh接続して行います。
(3台のインスタンス全てで同じコマンドを実行する必要があります)
tmuxを使えば、容易に複数のインスタンスで同時にコマンドを実行できます。詳細はこちらをご覧ください。

etcdのクラスタメンバーの起動

ここからの手順は各コントローラーインスタンスで行います

etcdのダウンロードとインストール

etcdのバイナリをgithubからダウンロードします。

wget -q --show-progress --https-only --timestamping \
  "https://github.com/etcd-io/etcd/releases/download/v3.5.4/etcd-v3.5.4-linux-amd64.tar.gz"

ダウンロードしたファイルを展開してetcdサーバーとetcdctlのコマンドラインユーティリティを抽出します。

tar -xvf etcd-v3.5.4-linux-amd64.tar.gz
sudo mv etcd-v3.5.4-linux-amd64/etcd* /usr/local/bin/

etcdサーバーの設定を行います。

sudo mkdir -p /etc/etcd /var/lib/etcd
sudo chmod 700 /var/lib/etcd
sudo cp ca.pem kubernetes-key.pem kubernetes.pem /etc/etcd/

インスタンスの内部IPアドレスは、クライアントのリクエストを受け付けて、etcdクラスタ間で通信するために使います。

現在のEC2インスタンスの内部IPアドレスを取得します。

INTERNAL_IP=$(curl -s http://169.254.169.254/latest/meta-data/local-ipv4)
echo ${INTERNAL_IP}

各etcdのメンバーは、etcdクラスター内で名前はユニークにする必要があります。このチュートリアルでは、現在使用しているEC2インスタンスのホスト名をetcdの名前として設定します。

ETCD_NAME=$(curl -s http://169.254.169.254/latest/user-data/ \
  | tr "|" "\n" | grep "^name" | cut -d"=" -f2)
echo "${ETCD_NAME}"

etcd.serviceとしてsystemdのユニットファイルを作成します。

cat <<EOF | sudo tee /etc/systemd/system/etcd.service
[Unit]
Description=etcd
Documentation=https://github.com/coreos

[Service]
Type=notify
ExecStart=/usr/local/bin/etcd \\
  --name ${ETCD_NAME} \\
  --cert-file=/etc/etcd/kubernetes.pem \\
  --key-file=/etc/etcd/kubernetes-key.pem \\
  --peer-cert-file=/etc/etcd/kubernetes.pem \\
  --peer-key-file=/etc/etcd/kubernetes-key.pem \\
  --trusted-ca-file=/etc/etcd/ca.pem \\
  --peer-trusted-ca-file=/etc/etcd/ca.pem \\
  --peer-client-cert-auth \\
  --client-cert-auth \\
  --initial-advertise-peer-urls https://${INTERNAL_IP}:2380 \\
  --listen-peer-urls https://${INTERNAL_IP}:2380 \\
  --listen-client-urls https://${INTERNAL_IP}:2379,https://127.0.0.1:2379 \\
  --advertise-client-urls https://${INTERNAL_IP}:2379 \\
  --initial-cluster-token etcd-cluster-0 \\
  --initial-cluster controller-0=https://10.0.1.10:2380,controller-1=https://10.0.1.11:2380,controller-2=https://10.0.1.12:2380 \\
  --initial-cluster-state new \\
  --data-dir=/var/lib/etcd
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

etcdサーバーの起動

sudo systemctl daemon-reload
sudo systemctl enable etcd
sudo systemctl start etcd

ここまでの手順を、各コントローラーノードcontroller-0,controller-1,controller-2で実行してください。

確認

etcdのクラスタメンバーを一覧表示します。

sudo ETCDCTL_API=3 etcdctl member list \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/etcd/ca.pem \
  --cert=/etc/etcd/kubernetes.pem \
  --key=/etc/etcd/kubernetes-key.pem

出力例

3a57933972cb5131, started, controller-2, https://10.240.0.12:2380, https://10.240.0.12:2379, false
f98dc20bce6225a0, started, controller-0, https://10.240.0.10:2380, https://10.240.0.10:2379, false
ffed16798470cab5, started, controller-1, https://10.240.0.11:2380, https://10.240.0.11:2379, false

08-Kubernetesコントロールプレーンの起動

このセクションでは、3つのインスタンスを使って可用性の高いKubernetesコントロールプレーンを作成します。合わせて、Kubernetes API Serverを外部クライアントに公開する外部ロードバランサーも作成します。
各ノードに、Kubernetes API Server、Scheduler、およびController Managerのコンポーネントをインストールします。

事前確認

各コントローラノードにログインする前に、自身の端末でKubernetesクラスタの外部公開用のFQDNを確認する必要があります。

以下のコマンドを実行し、FQDNを確認します。

 echo ${KUBERNETES_PUBLIC_ADDRESS}

後続のステップで使用するため、出力結果をメモしてください。

作業対象

前のセクションと同様、このセクションでもcontroller-0,controller-1,controller-2の各コントローラインスタンスで実行する必要があります。全てのコントローラーノードにsshコマンドでログインしてコマンドを実行します。既に各コントローラーノードにログインしている状態であれば、次の「Kubernetesコントロールプレーンのプロビジョニング」に飛んでください

for instance in controller-0 controller-1 controller-2; do
  external_ip=$(aws ec2 describe-instances \
    --filters "Name=tag:Name,Values=${instance}" \
    --output text --query 'Reservations[].Instances[].PublicIpAddress')

  echo ssh -i kubernetes.id_rsa ubuntu@$external_ip
done

ここからの手順は、直前のコマンドによって出力されたそれぞれのIPアドレスにssh接続して行います。
(3台のインスタンス全てで同じコマンドを実行する必要があります)
tmuxを使えば、容易に複数のインスタンスで同時にコマンドを実行できます。詳細はこちらをご覧ください。

Kubernetesコントロールプレーンのプロビジョニング

Kubernetesのconfigファイルを配置するためのディレクトリを作成します。

sudo mkdir -p /etc/kubernetes/config

KubernetesコントローラーのバイナリのDLとインストール

Kubernetesの公式のリリースバイナリをダウンロードします

wget -q --show-progress --https-only --timestamping \
  "https://storage.googleapis.com/kubernetes-release/release/v1.24.0/bin/linux/amd64/kube-apiserver" \
  "https://storage.googleapis.com/kubernetes-release/release/v1.24.0/bin/linux/amd64/kube-controller-manager" \
  "https://storage.googleapis.com/kubernetes-release/release/v1.24.0/bin/linux/amd64/kube-scheduler" \
  "https://storage.googleapis.com/kubernetes-release/release/v1.24.0/bin/linux/amd64/kubectl"

ダウンロードしたバイナリを移動させます。

chmod +x kube-apiserver kube-controller-manager kube-scheduler kubectl
sudo mv kube-apiserver kube-controller-manager kube-scheduler kubectl /usr/local/bin/

KubernetesAPIサーバーの設定


sudo mkdir -p /var/lib/kubernetes/

sudo mv ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem \
  service-account-key.pem service-account.pem \
  encryption-config.yaml /var/lib/kubernetes/

APIサーバーをクラスターのメンバーに知らせるための設定として、インスタンスの内部IPアドレスを使います。現在のEC2インスタンスの内部IPアドレスを取得します。

INTERNAL_IP=$(curl -s http://169.254.169.254/latest/meta-data/local-ipv4)
echo ${INTERNAL_IP}

kube-apiserver.serviceのsystemdのユニットファイルを生成します。

KUBERNETES_PUBLIC_ADDRESS=[事前確認の出力結果](#事前確認)
cat <<EOF | sudo tee /etc/systemd/system/kube-apiserver.service
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-apiserver \\
  --advertise-address=${INTERNAL_IP} \\
  --allow-privileged=true \\
  --apiserver-count=3 \\
  --audit-log-maxage=30 \\
  --audit-log-maxbackup=3 \\
  --audit-log-maxsize=100 \\
  --audit-log-path=/var/log/audit.log \\
  --authorization-mode=Node,RBAC \\
  --bind-address=0.0.0.0 \\
  --client-ca-file=/var/lib/kubernetes/ca.pem \\
  --enable-admission-plugins=NamespaceLifecycle,NodeRestriction,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota \\
  --etcd-cafile=/var/lib/kubernetes/ca.pem \\
  --etcd-certfile=/var/lib/kubernetes/kubernetes.pem \\
  --etcd-keyfile=/var/lib/kubernetes/kubernetes-key.pem \\
  --etcd-servers=https://10.0.1.10:2379,https://10.0.1.11:2379,https://10.0.1.12:2379 \\
  --event-ttl=1h \\
  --encryption-provider-config=/var/lib/kubernetes/encryption-config.yaml \\
  --kubelet-certificate-authority=/var/lib/kubernetes/ca.pem \\
  --kubelet-client-certificate=/var/lib/kubernetes/kubernetes.pem \\
  --kubelet-client-key=/var/lib/kubernetes/kubernetes-key.pem \\
  --runtime-config='api/all=true' \\
  --service-account-key-file=/var/lib/kubernetes/service-account.pem \\
  --service-account-signing-key-file=/var/lib/kubernetes/service-account-key.pem \\
  --service-account-issuer=https://${KUBERNETES_PUBLIC_ADDRESS}:443 \\
  --service-cluster-ip-range=10.32.0.0/24 \\
  --service-node-port-range=30000-32767 \\
  --tls-cert-file=/var/lib/kubernetes/kubernetes.pem \\
  --tls-private-key-file=/var/lib/kubernetes/kubernetes-key.pem \\
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

以下を参考にしています。

kube-apiserverkube-apiserver Synopsis The Kubernetes API server validates and configures d kubernetes.io

Kubernetesのコントローラーマネージャーの設定

kube-controller-managerのkubeconfigを移動させます。

sudo mv kube-controller-manager.kubeconfig /var/lib/kubernetes/

kube-controller-manager.serviceのsystemdユニットファイルを生成します。

cat <<EOF | sudo tee /etc/systemd/system/kube-controller-manager.service
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-controller-manager \\
  --bind-address=0.0.0.0 \\
  --cluster-cidr=10.200.0.0/16 \\
  --cluster-name=kubernetes \\
  --cluster-signing-cert-file=/var/lib/kubernetes/ca.pem \\
  --cluster-signing-key-file=/var/lib/kubernetes/ca-key.pem \\
  --kubeconfig=/var/lib/kubernetes/kube-controller-manager.kubeconfig \\
  --leader-elect=true \\
  --root-ca-file=/var/lib/kubernetes/ca.pem \\
  --service-account-private-key-file=/var/lib/kubernetes/service-account-key.pem \\
  --service-cluster-ip-range=10.32.0.0/24 \\
  --use-service-account-credentials=true \\
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

Kubernetesのschedulerの設定

sudo mkdir -p /etc/kubernetes/config/

kube-schedulerのkubeconfigを移動させます。

sudo mv kube-scheduler.kubeconfig /var/lib/kubernetes/

kube-scheduler.yamlを作成します。

cat <<EOF | sudo tee /etc/kubernetes/config/kube-scheduler.yaml
apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration
clientConnection:
  kubeconfig: "/var/lib/kubernetes/kube-scheduler.kubeconfig"
leaderElection:
  leaderElect: true
EOF

Version1.24から、apiVersion v1beta1が利用できなくなっています。

kube-scheduler.serviceのsystemdユニットファイルを生成します。

cat <<EOF | sudo tee /etc/systemd/system/kube-scheduler.service
[Unit]
Description=Kubernetes Scheduler
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-scheduler \\
  --config=/etc/kubernetes/config/kube-scheduler.yaml \\
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

コントローラーサービスの起動

sudo systemctl daemon-reload
sudo systemctl enable kube-apiserver kube-controller-manager kube-scheduler
sudo systemctl start kube-apiserver kube-controller-manager kube-scheduler

Kubernetes APIサーバーは初期化が完了するまで30秒くらいかかります。

確認

コントローラの各コンポーネントの状態を確認します。

kubectl cluster-info --kubeconfig admin.kubeconfig

出力例

Kubernetes control plane is running at https://127.0.0.1:6443

ホストファイルのエントリー追加

kubectl execコマンドを実行する際、コントローラノードはそれぞれのワーカーノードの名前解決を行う必要があります。

ホストインスタンスのhostsファイルに、ワーカーノードのホストエントリーを手動で追加します。

cat <<EOF | sudo tee -a /etc/hosts
10.0.1.20 ip-10-0-1-20
10.0.1.21 ip-10-0-1-21
10.0.1.22 ip-10-0-1-22
EOF

この手順を実行しないと、後続のテストが失敗します。

kubelet認証のRBAC設定

このステップでは、Kubernetes APIサーバーが各ワーカーノードのKubelet APIにアクセスできるようにRBACによるアクセス許可を設定します。
メトリックやログの取得、Pod内でのコマンドの実行には、Kubernetes APIサーバーからKubelet APIへのアクセスが必要です。

このチュートリアルでは、Kubelet authorization-modeフラグをWebhookに設定します。Webhookモードは SubjectAccessReview APIを使用して認証を行います。
ここで実行する以下のコマンドはクラスター全体に作用します。
任意のコントローラーノードにログインして一度だけ実行してください。

以下では、自身の端末からcontroller-0ノードにログインする所から手順を記載しています。

external_ip=$(aws ec2 describe-instances \
    --filters "Name=tag:Name,Values=controller-0" \
    --output text --query 'Reservations[].Instances[].PublicIpAddress')

ssh -i kubernetes.id_rsa ubuntu@${external_ip}
kube-apiserver-to-kubeletという名前でClusterRoleを作ります。

このロールに、Kubelet APIにアクセスしてポッドの管理に関連するタスクを実行する権限を付与します。

cat <<EOF | kubectl apply --kubeconfig admin.kubeconfig -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: system:kube-apiserver-to-kubelet
rules:
  - apiGroups:
      - ""
    resources:
      - nodes/proxy
      - nodes/stats
      - nodes/log
      - nodes/spec
      - nodes/metrics
    verbs:
      - "*"
EOF

Kubernetes APIサーバーは、–kubelet-client-certificateフラグで定義したクライアント証明書を使って、kubernetesユーザーとしてKubeletに対して認証を行います。

system:kube-apiserver-to-kubeletClusterRolekubernetesユーザーにバインドします。

cat <<EOF | kubectl apply --kubeconfig admin.kubeconfig -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: system:kube-apiserver
  namespace: ""
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:kube-apiserver-to-kubelet
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind: User
    name: kubernetes
EOF

Kubernetesクラスターのパブリックエンドポイントを有効化する

以下のコマンドは、EC2インスタンスではなく自身のMacもしくはLinux環境のラップトップで行います。
kubernetes-the-hard-wayロードバランサーのアドレスを取得します。

KUBERNETES_PUBLIC_ADDRESS=$(aws elbv2 describe-load-balancers \
  --load-balancer-arns ${LOAD_BALANCER_ARN} \
  --output text --query 'LoadBalancers[].DNSName')
echo ${KUBERNETES_PUBLIC_ADDRESS}

HTTPリクエストを作成しKubernetesのVersion情報を取得します。

curl -k --cacert ca.pem https://${KUBERNETES_PUBLIC_ADDRESS}/version

出力例

{
  "major": "1",
  "minor": "24",
  "gitVersion": "v1.24.0",
  "gitCommit": "4ce5a8954017644c5420bae81d72b09b735c21f0",
  "gitTreeState": "clean",
  "buildDate": "2022-05-03T13:38:19Z",
  "goVersion": "go1.18.1",
  "compiler": "gc",
  "platform": "linux/amd64"
}%             

09-ワーカーノードの起動

このセクションでは、3つのKubernetesワーカーノードをプロビジョニングします。以下のコンポーネントを各ノードにインストールします。

  • runc
  • gVisor
  • container networking plugins
  • containerd
  • kubelet
  • kube-proxy

準備

この手順で記載されているコマンドは、worker-0,worker-1worker-2の各ワーカーノードで実行する必要があります。

まずはsshコマンドで各ワーカーノードにログインします

for instance in worker-0 worker-1 worker-2; do
  external_ip=$(aws ec2 describe-instances \
    --filters "Name=tag:Name,Values=${instance}" \
    --output text --query 'Reservations[].Instances[].PublicIpAddress')

  echo ssh -i kubernetes.id_rsa ubuntu@$external_ip
done

ここからの手順は、直前のコマンドによって出力されたそれぞれのIPアドレスにssh接続して行います。
(3台のインスタンス全てで同じコマンドを実行する必要があります)
tmuxを使えば、容易に複数のインスタンスで同時にコマンドを実行できます。詳細はこちらをご覧ください。

Kubernetesのワーカーノードのプロビジョング

使用するライブラリをインストールします。

sudo apt-get update
sudo apt-get -y install socat conntrack ipset

socatkubectl port-forwardコマンドに必要なバイナリです。

swapの無効化

swapが有効になっている場合、kubeletの起動に失敗します。
以下のコマンドでswapが有効になっているかどうかを確認します。

sudo swapon --show

出力が何もない場合はswapは無効化されています。
有効になっている場合は以下のコマンドでswapを無効化します。

sudo swapoff -a

ワーカーノードに必要なコンポーネントのバイナリをダウンロードしてインストールします。

wget -q --show-progress --https-only --timestamping \
  https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.24.0/crictl-v1.24.0-linux-amd64.tar.gz \
  https://github.com/opencontainers/runc/releases/download/v1.1.3/runc.amd64 \
  https://github.com/containernetworking/plugins/releases/download/v1.1.1/cni-plugins-linux-amd64-v1.1.1.tgz \
  https://github.com/containerd/containerd/releases/download/v1.6.6/containerd-1.6.6-linux-amd64.tar.gz \
  https://storage.googleapis.com/kubernetes-release/release/v1.24.0/bin/linux/amd64/kubectl \
  https://storage.googleapis.com/kubernetes-release/release/v1.24.0/bin/linux/amd64/kube-proxy \
  https://storage.googleapis.com/kubernetes-release/release/v1.24.0/bin/linux/amd64/kubelet

インストールする先のディレクトリを作成します。

sudo mkdir -p \
  /etc/cni/net.d \
  /opt/cni/bin \
  /var/lib/kubelet \
  /var/lib/kube-proxy \
  /var/lib/kubernetes \
  /var/run/kubernetes

ワーカーのバイナリを展開します。

mkdir containerd
tar -xvf crictl-v1.24.0-linux-amd64.tar.gz
tar -xvf containerd-1.6.6-linux-amd64.tar.gz -C containerd
sudo tar -xvf cni-plugins-linux-amd64-v1.1.1.tgz -C /opt/cni/bin/
sudo mv runc.amd64 runc
chmod +x crictl kubectl kube-proxy kubelet runc 
sudo mv crictl kubectl kube-proxy kubelet runc /usr/local/bin/
sudo mv containerd/bin/* /bin/

CNIネットワーキングの設定

EC2インスタンスのPod用に設定されているCIDR範囲を取得します。

POD_CIDR=$(curl -s http://169.254.169.254/latest/user-data/ \
  | tr "|" "\n" | grep "^pod-cidr" | cut -d"=" -f2)
echo "${POD_CIDR}"

bridgeネットワークの設定ファイルを作ります。

cat <<EOF | sudo tee /etc/cni/net.d/10-bridge.conf
{
    "cniVersion": "1.0.0",
    "name": "bridge",
    "type": "bridge",
    "bridge": "cnio0",
    "isGateway": true,
    "ipMasq": true,
    "ipam": {
        "type": "host-local",
        "ranges": [
          [{"subnet": "${POD_CIDR}"}]
        ],
        "routes": [{"dst": "0.0.0.0/0"}]
    }
}
EOF

loopbackネットワークの設定ファイルを作ります。

cat <<EOF | sudo tee /etc/cni/net.d/99-loopback.conf
{
    "cniVersion": "1.0.0",
    "name": "lo",
    "type": "loopback"
}
EOF

containerdの設定

containerdの設定ファイルを作成します。

sudo mkdir -p /etc/containerd/
cat << EOF | sudo tee /etc/containerd/config.toml
[plugins]
  [plugins.cri.containerd]
    snapshotter = "overlayfs"
    [plugins.cri.containerd.default_runtime]
      runtime_type = "io.containerd.runtime.v1.linux"
      runtime_engine = "/usr/local/bin/runc"
      runtime_root = ""
EOF

containerd.serviceのsystemdユニットファイルを作成します。

cat <<EOF | sudo tee /etc/systemd/system/containerd.service
[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target

[Service]
ExecStartPre=/sbin/modprobe overlay
ExecStart=/bin/containerd
Restart=always
RestartSec=5
Delegate=yes
KillMode=process
OOMScoreAdjust=-999
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity

[Install]
WantedBy=multi-user.target
EOF

Kubeletの設定

WORKER_NAME=$(curl -s http://169.254.169.254/latest/user-data/ \
| tr "|" "\n" | grep "^name" | cut -d"=" -f2)
echo "${WORKER_NAME}"

sudo mv ${WORKER_NAME}-key.pem ${WORKER_NAME}.pem /var/lib/kubelet/
sudo mv ${WORKER_NAME}.kubeconfig /var/lib/kubelet/kubeconfig
sudo mv ca.pem /var/lib/kubernetes/

kubelet-config.yaml設定ファイルを作ります。

cat <<EOF | sudo tee /var/lib/kubelet/kubelet-config.yaml
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
authentication:
  anonymous:
    enabled: false
  webhook:
    enabled: true
  x509:
    clientCAFile: "/var/lib/kubernetes/ca.pem"
authorization:
  mode: Webhook
clusterDomain: "cluster.local"
clusterDNS:
  - "10.32.0.10"
podCIDR: "${POD_CIDR}"
resolvConf: "/run/systemd/resolve/resolv.conf"
runtimeRequestTimeout: "15m"
tlsCertFile: "/var/lib/kubelet/${WORKER_NAME}.pem"
tlsPrivateKeyFile: "/var/lib/kubelet/${WORKER_NAME}-key.pem"
EOF

resolvconfsystemd-resolvを使用してCoreDNSを実行するときにループを回避するために使用されます。

kubelet.servicesystemdユニットファイルを作成します。

cat <<EOF | sudo tee /etc/systemd/system/kubelet.service
[Unit]
Description=Kubernetes Kubelet
Documentation=https://github.com/kubernetes/kubernetes
After=containerd.service
Requires=containerd.service

[Service]
ExecStart=/usr/local/bin/kubelet \\
  --config=/var/lib/kubelet/kubelet-config.yaml \\
  --container-runtime=remote \\
  --container-runtime-endpoint=unix:///var/run/containerd/containerd.sock \\
  --kubeconfig=/var/lib/kubelet/kubeconfig \\
  --register-node=true \\
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

尚、Version1.24から以下のオプションが削除されています。
–image-pull-progress-deadline=2m
–network-plugin=cni

Kubernetes Proxyの設定

sudo mv kube-proxy.kubeconfig /var/lib/kube-proxy/kubeconfig

kube-proxy-config.yaml設定ファイルを作成します。

cat <<EOF | sudo tee /var/lib/kube-proxy/kube-proxy-config.yaml
kind: KubeProxyConfiguration
apiVersion: kubeproxy.config.k8s.io/v1alpha1
clientConnection:
  kubeconfig: "/var/lib/kube-proxy/kubeconfig"
mode: "iptables"
clusterCIDR: "10.200.0.0/16"
EOF

kube-proxy.servicesystemdユニットファイルを作成します。

cat <<EOF | sudo tee /etc/systemd/system/kube-proxy.service
[Unit]
Description=Kubernetes Kube Proxy
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-proxy \\
  --config=/var/lib/kube-proxy/kube-proxy-config.yaml
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

ワーカーのサービス群の起動

sudo systemctl daemon-reload
sudo systemctl enable containerd kubelet kube-proxy
sudo systemctl start containerd kubelet kube-proxy

ここまでの手順を各ワーカーノード、worker-0worker-1worker-2で実行します。

確認

以下のコマンドは、自身のMacもしくはLinux端末から実行します。
登録されているKubernetesノードの一覧を表示させます。

external_ip=$(aws ec2 describe-instances --filters \
    "Name=tag:Name,Values=controller-0" \
    "Name=instance-state-name,Values=running" \
    --output text --query 'Reservations[].Instances[].PublicIpAddress')

ssh -i kubernetes.id_rsa ubuntu@${external_ip} kubectl get nodes --kubeconfig admin.kubeconfig

出力例

NAME           STATUS   ROLES    AGE   VERSION
ip-10-0-1-20   Ready    <none>   32s   v1.24.0
ip-10-0-1-21   Ready    <none>   29s   v1.24.0
ip-10-0-1-22   Ready    <none>   26s   v1.24.0

10-リモートアクセス用のkubectl設定

このセクションでは、adminユーザーのcredenialに基づいた、kubectlコマンドラインユーティリティ用のkubeconfigファイルを生成します。
adminのクライアント証明書の生成に使用したディレクトリと同じディレクトリでコマンドを実行してください。

Admin Kubernetes設定ファイルを生成

各kubeconfigは、Kubernetes APIサーバーと接続できる必要があります。
高可用性を実現するために、Kubernetes APIサーバーの前に設置した外部ロードバランサーに割り当てられたIPアドレスを使用します。

adminユーザーを認証するkubeconfigファイルを生成します。

KUBERNETES_PUBLIC_ADDRESS=$(aws elbv2 describe-load-balancers \
--load-balancer-arns ${LOAD_BALANCER_ARN} \
--output text --query 'LoadBalancers[].DNSName')

kubectl config set-cluster kubernetes-the-hard-way \
  --certificate-authority=ca.pem \
  --embed-certs=true \
  --server=https://${KUBERNETES_PUBLIC_ADDRESS}:443

kubectl config set-credentials admin \
  --client-certificate=admin.pem \
  --client-key=admin-key.pem

kubectl config set-context kubernetes-the-hard-way \
  --cluster=kubernetes-the-hard-way \
  --user=admin

kubectl config use-context kubernetes-the-hard-way

確認

remoteのKubernetesクラスタのノードの一覧を取得します。

kubectl get nodes

出力例

NAME           STATUS   ROLES    AGE   VERSION
ip-10-0-1-20   Ready    <none>   89m   v1.24.0
ip-10-0-1-21   Ready    <none>   89m   v1.24.0
ip-10-0-1-22   Ready    <none>   89m   v1.24.0

11-クラスタ内ネットワークの設定

ノードにスケジュールされたPodは、ノードのPod CIDR範囲からIPアドレスを受け取ります。この時点では、ネットワークルートが見つからないため、Podは異なるノードで実行されている他のPodと通信できません。
このセクションでは、ノードのPod CIDR範囲をノードの内部IPアドレスにマップするための、各ワーカーノードのルートを作成します。
本チュートリアルで紹介するモデルは一例であり、Kubernetesのネットワークモデルの実装は他にもあります。

ルーティングテーブルとルートを定義

このセクションでは、kubernetes-the-hard-wayVPCネットワーク内でルートを作るために必要な情報を集めます。
通常、この機能はflannel, calico, amazon-vpc-cin-k8s等のCNIプラグインによって提供されます。
これを手作業で行うことにより、これらのプラグインがバックグラウンドで何をしているのかを理解しやすくなります。
まずは各ワーカーインスタンスの内部IPアドレスとPod CIDR範囲を表示させます。

for instance in worker-0 worker-1 worker-2; do
  instance_id_ip="$(aws ec2 describe-instances \
    --filters "Name=tag:Name,Values=${instance}" \
    --output text --query 'Reservations[].Instances[].[InstanceId,PrivateIpAddress]')"
  instance_id="$(echo "${instance_id_ip}" | cut -f1)"
  instance_ip="$(echo "${instance_id_ip}" | cut -f2)"
  pod_cidr="$(aws ec2 describe-instance-attribute \
    --instance-id "${instance_id}" \
    --attribute userData \
    --output text --query 'UserData.Value' \
    | base64 --decode | tr "|" "\n" | grep "^pod-cidr" | cut -d'=' -f2)"
  echo "${instance_ip} ${pod_cidr}"

  aws ec2 create-route \
    --route-table-id "${ROUTE_TABLE_ID}" \
    --destination-cidr-block "${pod_cidr}" \
    --instance-id "${instance_id}"
done

出力例

10.0.1.20 10.200.0.0/24
{
    "Return": true
}
10.0.1.21 10.200.1.0/24
{
    "Return": true
}
10.0.1.22 10.200.2.0/24
{
    "Return": true
}

ルートの確認

各ワーカーインスタンスに対してネットワークルートを確認します。

aws ec2 describe-route-tables \
  --route-table-ids "${ROUTE_TABLE_ID}" \
  --query 'RouteTables[].Routes'

出力例

[
    [
        {
            "DestinationCidrBlock": "10.200.0.0/24",
            "InstanceId": "i-0cb0e48788838daa4",
            "InstanceOwnerId": "523358537305",
            "NetworkInterfaceId": "eni-0d5ff998bd2fb09c5",
            "Origin": "CreateRoute",
            "State": "active"
        },
        {
            "DestinationCidrBlock": "10.200.1.0/24",
            "InstanceId": "i-001c9deec822b1325",
            "InstanceOwnerId": "523358537305",
            "NetworkInterfaceId": "eni-04334cbdcbcf2cfd5",
            "Origin": "CreateRoute",
            "State": "active"
        },
        {
            "DestinationCidrBlock": "10.200.2.0/24",
            "InstanceId": "i-0055e9af229f7ea5d",
            "InstanceOwnerId": "523358537305",
            "NetworkInterfaceId": "eni-0c28e31352ccd881a",
            "Origin": "CreateRoute",
            "State": "active"
        },
        {
            "DestinationCidrBlock": "10.0.0.0/16",
            "GatewayId": "local",
            "Origin": "CreateRouteTable",
            "State": "active"
        },
        {
            "DestinationCidrBlock": "0.0.0.0/0",
            "GatewayId": "igw-0f1932111bd00691f",
            "Origin": "CreateRoute",
            "State": "active"
        }
    ]
]

12-DNSクラスタアドオンの導入

このセクションでは、Kubernetesクラスタ内で動いているアプリケーションに、CoreDNSを使ったDNSベースのサービスディスカバリを提供するDNSアドオンをデプロイします。

DNSクラスターアドオン

corednsクラスターアドオンをデプロイします。

cat > coredns-1.9.yaml <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: coredns
  namespace: kube-system

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: system:coredns
rules:
  - apiGroups:
    - ""
    resources:
    - endpoints
    - services
    - pods
    - namespaces
    verbs:
    - list
    - watch
  - apiGroups:
    - discovery.k8s.io
    resources:
    - endpointslices
    verbs:
    - list
    - watch

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: system:coredns
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:coredns
subjects:
- kind: ServiceAccount
  name: coredns
  namespace: kube-system

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health {
          lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
          fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        cache 30
        loop
        reload
        loadbalance
    }

apiVersion: apps/v1
kind: Deployment
metadata:
  name: coredns
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    kubernetes.io/name: "CoreDNS"
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
  selector:
    matchLabels:
      k8s-app: kube-dns
  template:
    metadata:
      labels:
        k8s-app: kube-dns
    spec:
      priorityClassName: system-cluster-critical
      serviceAccountName: coredns
      tolerations:
        - key: "CriticalAddonsOnly"
          operator: "Exists"
      nodeSelector:
        kubernetes.io/os: linux
      affinity:
         podAntiAffinity:
           requiredDuringSchedulingIgnoredDuringExecution:
           - labelSelector:
               matchExpressions:
               - key: k8s-app
                 operator: In
                 values: ["kube-dns"]
             topologyKey: kubernetes.io/hostname
      containers:
      - name: coredns
        image: coredns/coredns:1.9.3
        imagePullPolicy: IfNotPresent
        resources:
          limits:
            memory: 170Mi
          requests:
            cpu: 100m
            memory: 70Mi
        args: [ "-conf", "/etc/coredns/Corefile" ]
        volumeMounts:
        - name: config-volume
          mountPath: /etc/coredns
          readOnly: true
        ports:
        - containerPort: 53
          name: dns
          protocol: UDP
        - containerPort: 53
          name: dns-tcp
          protocol: TCP
        - containerPort: 9153
          name: metrics
          protocol: TCP
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            add:
            - NET_BIND_SERVICE
            drop:
            - all
          readOnlyRootFilesystem: true
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 60
          timeoutSeconds: 5
          successThreshold: 1
          failureThreshold: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: 8181
            scheme: HTTP
      dnsPolicy: Default
      volumes:
        - name: config-volume
          configMap:
            name: coredns
            items:
            - key: Corefile
              path: Corefile

apiVersion: v1
kind: Service
metadata:
  name: kube-dns
  namespace: kube-system
  annotations:
    prometheus.io/port: "9153"
    prometheus.io/scrape: "true"
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: "CoreDNS"
spec:
  selector:
    k8s-app: kube-dns
  clusterIP: 10.32.0.10
  ports:
  - name: dns
    port: 53
    protocol: UDP
  - name: dns-tcp
    port: 53
    protocol: TCP
  - name: metrics
    port: 9153
    protocol: TCP
EOF
kubectl apply -f coredns-1.9.yaml

出力

serviceaccount/coredns created
clusterrole.rbac.authorization.k8s.io/system:coredns created
clusterrolebinding.rbac.authorization.k8s.io/system:coredns created
configmap/coredns created
deployment.apps/coredns created
service/kube-dns created

kube-dns deploymentによって作られたPodの確認を行います。

kubectl get pods -l k8s-app=kube-dns -n kube-system

出力例

NAME                       READY   STATUS    RESTARTS   AGE
coredns-8494f9c688-jth4j   1/1     Running   0          46s
coredns-8494f9c688-p679g   1/1     Running   0          46s

確認

busybox deploymentを作成します。

kubectl run busybox --image=busybox:1.28 --command -- sleep 3600

busybox deploymentによって作られてPodを確認します。

kubectl get pods -l run=busybox

出力例

NAME      READY   STATUS    RESTARTS   AGE
busybox   1/1     Running   0          25s

busybox pod内からkubernetesserviceのDNS lookupを行います。

POD_NAME=$(kubectl get pods -l run=busybox -o jsonpath="{.items[0].metadata.name}")
echo ${POD_NAME}

kubectl exec -ti $POD_NAME -- nslookup kubernetes

出力例

ubernetes
Server:    10.32.0.10
Address 1: 10.32.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes
Address 1: 10.32.0.1 kubernetes.default.svc.cluster.local

13-スモークテスト

このセクションでは、Kubernetesクラスタが正しく機能していることを確認するためのタスクを実行します。

データの暗号化

このステップでは保存されているデータの暗号化を確認します。
generic secretを作ります。

kubectl create secret generic kubernetes-the-hard-way \
  --from-literal="mykey=mydata"

etcdに保存されているkubernetes-the-hard-wayのsecretをhexdumpします。

external_ip=$(aws ec2 describe-instances --filters \
  "Name=tag:Name,Values=controller-0" \
  "Name=instance-state-name,Values=running" \
  --output text --query 'Reservations[].Instances[].PublicIpAddress')

ssh -i kubernetes.id_rsa ubuntu@${external_ip} \
 "sudo ETCDCTL_API=3 etcdctl get \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/etcd/ca.pem \
  --cert=/etc/etcd/kubernetes.pem \
  --key=/etc/etcd/kubernetes-key.pem\
  /registry/secrets/default/kubernetes-the-hard-way | hexdump -C"

出力例

00000000  2f 72 65 67 69 73 74 72  79 2f 73 65 63 72 65 74  |/registry/secret|
00000010  73 2f 64 65 66 61 75 6c  74 2f 6b 75 62 65 72 6e  |s/default/kubern|
00000020  65 74 65 73 2d 74 68 65  2d 68 61 72 64 2d 77 61  |etes-the-hard-wa|
00000030  79 0a 6b 38 73 3a 65 6e  63 3a 61 65 73 63 62 63  |y.k8s:enc:aescbc|
00000040  3a 76 31 3a 6b 65 79 31  3a 67 3f 76 23 d3 0f 9b  |:v1:key1:g?v#...|
00000050  c2 92 14 54 6e 7f 26 41  a6 27 e0 a7 d6 9e 3f 67  |...Tn.&A.'....?g|
00000060  07 88 36 c9 99 ac dd e5  5f 44 e5 f0 7e 45 9b 0a  |..6....._D..~E..|
00000070  04 ed 0c b8 77 0b a7 29  7c df 34 ec 4c 22 d6 36  |....w..)|.4.L".6|
00000080  f7 58 38 b9 5f 49 1f 0f  b8 ac a6 ea 4d 23 95 0f  |.X8._I......M#..|
00000090  aa 35 c8 39 eb 33 e2 c8  4c 70 5e f8 2c 05 ef 88  |.5.9.3..Lp^.,...|
000000a0  cc 41 3f da d2 05 93 3a  3c 4d 1c 33 a2 fe 78 fb  |.A?....:<M.3..x.|
000000b0  ec fa 02 af cd c0 6d 8e  dd 6d b7 5a e2 b1 f7 44  |......m..m.Z...D|
000000c0  3c ec d9 04 7d 9b 82 5e  d4 22 fe 6f 5e 2b 47 aa  |<...}..^.".o^+G.|
000000d0  56 76 13 a0 9c a4 ca a6  c1 46 a1 5e 1b a6 ab 9b  |Vv.......F.^....|
000000e0  d8 71 e7 84 3c ed 94 a0  f6 b8 6e 11 2e 44 8e ab  |.q..<.....n..D..|
000000f0  0f f4 89 9a ac e6 cb f6  8f 48 da 8e 0e c2 ba cf  |.........H......|
00000100  c5 be 3f a4 c2 a0 38 29  78 23 a7 56 db b3 e0 20  |..?...8)x#.V... |
00000110  a3 ae d2 9b d7 8a 4b 3b  83 df ee 12 c5 71 1f e5  |......K;.....q..|
00000120  c6 5b 97 0a 98 02 9e 85  df db e2 70 44 37 35 b2  |.[.........pD75.|
00000130  a8 30 cf 79 b5 25 4b d3  7a 35 f6 cf 69 11 25 f2  |.0.y.%K.z5..i.%.|
00000140  bd 37 9e 2c 57 ed c0 d0  26 e0 8d b7 da bb 5e 76  |.7.,W...&.....^v|
00000150  0b e8 46 6d 6e 38 65 09  c2 0a                    |..Fmn8e...|
0000015a

etcdキーは、k8s:enc:aescbc:v1:key1というプレフィックスになっているはずです。これは、aescbcプロバイダがkey1という暗号化キーでデータを暗号化したことを表しています。

自端末からDeploymentの作成と管理

このステップではDeploymentの作成と管理ができているかを確認します。

nginx web serverのdeploymentを作成します。

kubectl create deployment nginx --image=nginx

nginx deploymentによってできたPodを確認します。

kubectl get pods -l app=nginx

出力例

NAME                     READY   STATUS    RESTARTS   AGE
nginx-6799fc88d8-qfjkw   1/1     Running   0          13s

Port Forwarding

このステップでは、port forwardingを使って外部からアプリケーションにアクセスできるかを確認します。nginx podのフルネームを取得します。

POD_NAME=$(kubectl get pods -l app=nginx -o jsonpath="{.items[0].metadata.name}")

ローカルの8080ポートをnginx Podの80番ポートにフォワードします。

kubectl port-forward $POD_NAME 8080:80

出力例

Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

別のターミナルからフォワードしたアドレスにHTTPリクエストを投げてみます。

curl --head http://127.0.0.1:8080

出力例

HTTP/1.1 200 OK
Server: nginx/1.21.6
Date: Mon, 25 Apr 2022 05:07:07 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 25 Jan 2022 15:03:52 GMT
Connection: keep-alive
ETag: "61f01158-267"
Accept-Ranges: bytes

元のターミナルに戻ってnginx Podへのフォワーディングプロセスを止めます。

Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
Handling connection for 8080
^C

Logs

このステップでは、コンテナのログの取得ができるかを確認します。
nginx Podのログを表示します。

kubectl logs $POD_NAME

出力例

/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2022/04/25 05:05:55 [notice] 1#1: using the "epoll" event method
2022/04/25 05:05:55 [notice] 1#1: nginx/1.21.6
2022/04/25 05:05:55 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6) 
2022/04/25 05:05:55 [notice] 1#1: OS: Linux 5.13.0-1022-aws
2022/04/25 05:05:55 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2022/04/25 05:05:55 [notice] 1#1: start worker processes
2022/04/25 05:05:55 [notice] 1#1: start worker process 32
2022/04/25 05:05:55 [notice] 1#1: start worker process 33
127.0.0.1 - - [25/Apr/2022:05:07:07 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

Exec

このステップではコンテナ内でのコマンド実行ができるかを確認します。nginxコンテナに入って、nginx -vを実行してnginxのバージョンを表示します。

kubectl exec -ti $POD_NAME -- nginx -v

出力例

nginx version: nginx/1.21.6

Services

このステップでは、Serviceを使ったアプリケーションの公開ができるかを確認します。nginx deploymentをNodePort を使って公開します。

kubectl expose deployment nginx --port 80 --type NodePort

クラスターがクラウドプロバイダーインテグレーションの設定がされていないため、LoadBalancerは使用できません。この手順では、クラウドプロバイダーインテグレーションの設定は対象外です。

nginx serviceでアサインされたノードのポート取得します。

NODE_PORT=$(kubectl get svc nginx \
  --output=jsonpath='{range .spec.ports[0]}{.nodePort}')

nginxノードポートへのリモートアクセスを許可するファイアウォールのルール追加します。

aws ec2 authorize-security-group-ingress \
  --group-id ${SECURITY_GROUP_ID} \
  --protocol tcp \
  --port ${NODE_PORT} \
  --cidr 0.0.0.0/0

nginxのpodが実行されているワーカーノード名を取得します。

INSTANCE_NAME=$(kubectl get pod $POD_NAME --output=jsonpath='{.spec.nodeName}')

ワーカーインスタンスのExternal IPアドレスを取得します。

EXTERNAL_IP=$(aws ec2 describe-instances --filters \
    "Name=instance-state-name,Values=running" \
    "Name=network-interface.private-dns-name,Values=${INSTANCE_NAME}.*.internal*" \
    --output text --query 'Reservations[].Instances[].PublicIpAddress')

External IPとnginxのノードポートを使用してHTTPリクエストを送信します。

curl -I http://${EXTERNAL_IP}:${NODE_PORT}

出力例

HTTP/1.1 200 OK
Server: nginx/1.21.6
Date: Mon, 25 Apr 2022 05:12:16 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 25 Jan 2022 15:03:52 GMT
Connection: keep-alive
ETag: "61f01158-267"
Accept-Ranges: bytes

14-crictlを使用してワーカーノードのイメージ・ポッド・コンテナをチェックする

このセクションは、ワーカーノードにログインし、リソース一覧を確認します。このセクションで扱うコマンドは、立ち上がっている3台のワーカーノード全てで実行可能です。

external_ip=$(aws ec2 describe-instances \
  --filters "Name=tag:Name,Values=worker-0" \
  --output text --query 'Reservations[].Instances[].PublicIpAddress')
ssh -i kubernetes.id_rsa ubuntu@${external_ip}

以下のコマンドを実行し、出力を確認します。

sudo crictl -r unix:///var/run/containerd/containerd.sock images

出力例

IMAGE                       TAG                 IMAGE ID            SIZE
docker.io/coredns/coredns   1.8.3               3885a5b7f138c       12.9MB
docker.io/coredns/coredns   1.9.3               5185b96f0becf       14.8MB
docker.io/library/busybox   1.28                8c811b4aec35f       728kB
k8s.gcr.io/pause            3.6                 6270bb605e12e       302kB

以下のコマンドを実行し、出力を確認します。

sudo crictl -r unix:///var/run/containerd/containerd.sock pods

出力例

POD ID              CREATED             STATE               NAME                NAMESPACE           ATTEMPT             RUNTIME
948e94faa3aad       21 hours ago        Ready               busybox             default             0                   (default)

以下のコマンドを実行し、出力を確認します。

sudo crictl -r unix:///var/run/containerd/containerd.sock ps

出力例

CONTAINER           IMAGE               CREATED             STATE           NAME           ATTEMPT       POD ID         POD
65c829a4a642c       8c811b4aec35f       46 minutes ago      Running         busybox        20            948e94faa3aad   busybox

15-後片付け

このセクションでは、これまでに作成してきたAWSに関するリソースを削除します。

EC2インスタンス

コントローラーノード、ワーカーノードを削除します。

aws ec2 terminate-instances \
  --instance-ids \
    $(aws ec2 describe-instances \
      --filter "Name=tag:Name,Values=controller-0,controller-1,controller-2,worker-0,worker-1,worker-2" \
      --output text --query 'Reservations[].Instances[].InstanceId')
aws ec2 delete-key-pair --key-name kubernetes

Networking

外部ロードバランサー、VPC等のネットワークリソースを削除します。

aws elbv2 delete-load-balancer --load-balancer-arn "${LOAD_BALANCER_ARN}"
aws elbv2 delete-target-group --target-group-arn "${TARGET_GROUP_ARN}"
aws ec2 delete-security-group --group-id "${SECURITY_GROUP_ID}"
ROUTE_TABLE_ASSOCIATION_ID="$(aws ec2 describe-route-tables \
  --route-table-ids "${ROUTE_TABLE_ID}" \
  --output text --query 'RouteTables[].Associations[].RouteTableAssociationId')"
aws ec2 disassociate-route-table --association-id "${ROUTE_TABLE_ASSOCIATION_ID}"
aws ec2 delete-route-table --route-table-id "${ROUTE_TABLE_ID}"
aws ec2 detach-internet-gateway \
  --internet-gateway-id "${INTERNET_GATEWAY_ID}" \
  --vpc-id "${VPC_ID}"
aws ec2 delete-internet-gateway --internet-gateway-id "${INTERNET_GATEWAY_ID}"
aws ec2 delete-subnet --subnet-id "${SUBNET_ID}"
aws ec2 delete-vpc --vpc-id "${VPC_ID}"

16-おわりに

今回はツールを使わずにKubernetesのクラスタをAWSで構築する方法を紹介しました。

Kubernetesの初学者や資格試験対策のお役に立てれば幸いです。

関連記事

TOP