ラズパイで理解するkubernetes-the-hard-way
本記事の内容
Kubernetesの有名な学習リソースの一つにkubernetes the hard wayというものがあります。それはkubeadmやマネージドKubernetesサービスを使用せず、手作業でKubernetesクラスタを作成していき、Kubernetesを学ぶというコンテンツです。
kubernetes the hard wayではクラスタをGCPのCompute Engineで構築するのですが、本記事ではRaspberry piでクラスタ構築していきます。
概ねCyberAgentさんのリポジトリを参考にしましたが、以下の点でオリジナリティを加えています。
- CyberAgentさんのリポジトリは2020年に作成されており少し古い情報があるため、その内容を更新している
- kubernetes the hard wayでは、実施する各ステップについての説明が乏しいので、適宜補足や参照URLを加えて理解を促すようにしている
- 学習目的であるため、Premature Optimizationにならないように意識している。
本記事で使用している環境
ホスト: Raspberry Pi 4 ModelB/8GB
OS: Ubuntu Server 22.04.3 LTS
Kubernetes: v1.29.0
Kubernetesの概要がわからない人向け
Kubernetesの概要を理解するために役立つ情報源をまとめた記事を以前に書きましたので、それを参考に学習をしてみてください。全くわからない状態で行うより、ある程度わかる状態で行う方が学習効果があると思います。
作成するKubernetesクラスタ(物理)
こちらが作成していくKubernetesクラスタです。結構気に入っていて、愛着が湧いてきました。
使用した機材
機材名 | 数 | URL |
---|---|---|
Raspberry Pi 4 ModelB/8GB | 3 | https://raspberry-pi.ksyic.com/main/index/pdp.id/552/pdp.open/552 |
Raspberry Pi PoE+ HAT | 3 | https://raspberry-pi.ksyic.com/main/index/pdp.id/719/pdp.open/719 |
microSD 64GB | 3 | https://www.amazon.co.jp/dp/B08PTQMZY3 |
PoE Switch | 1 | https://www.amazon.co.jp/dp/B0763TGBTS |
LANケーブル 3m | 1 | https://www.amazon.co.jp/dp/B001BY5BVW |
LANケーブル 15cm | 3 | https://www.amazon.co.jp/dp/B00FZTNJQI/ |
microHDMI-HDMI変換ケーブル | 1 | https://www.amazon.co.jp/dp/B07QBZ667V |
ケース | 1 | https://www.amazon.co.jp/dp/B07TJ15YL1 |
M2.5ネジ 10mm | 12 | https://www.amazon.co.jp/gp/product/B01MY8E4FF |
その他モニタ, キーボードを使用しました。
PoE+ HATは給電をLANケーブルで行なうことで充電ケーブルを不要にし、配線をスッキリさせる機器なので、必須ではないです。
物理構築手順
CyberAgentさんのリポジトリを見ながら作成しました。
Kubernetesクラスタ構築手順
ここからkubernetes the hard wayを行なっていきます。クラスタの構成はマスターノード1台、ワーカーノード2台の計3台で行います。
大まかな手順
- 各コンポーネント通信のための証明書の作成
- マスターノード
- etcd, kube-apiserver, kube-controller-manager, kube-schedulerの設定
- ワーカーノード
- kubelet, コンテナランタイム, cniプラグインの設定
- デプロイの確認
準備
これからの説明は以下のような設定であることを前提に構築していきます。異なる点がある場合は各自の環境に合わせてください。
ip | hostname | 役割 |
---|---|---|
192.168.0.5 | master1 | master node |
192.168.0.7 | worker1 | worker node |
192.168.0.8 | worker2 | worker node |
hostnameは、hostnamectl
コマンドで設定することができます。
# 例
sudo hostnamectl set-hostname master1
- Pod用サブネット
-
10.10.0.0/16
- worker1:
10.10.1.0/24
- worker2:
10.10.2.0/24
- worker1:
-
- ClusterIP用サブネット
10.32.0.0/24
各ノードのipアドレスを固定したい場合は、 /etc/netplan/
に新しくyamlファイルを追加する形で実現できます。
参考: 【Ubuntu】固定IPアドレスを割り当てる
証明書の作成
Kubernetesは様々なコンポーネントで構成されており、それらの通信にTLSを使用します。そのための証明書の作成を行います
このステップはmasterノードで行います。適宜ssh接続を利用して行なってください。
まず証明書作成に使用するツール群をインストールします。
sudo apt install golang-cfssl
そして、以下のシェルスクリプト(generate-cert.sh
)を実行します。
generate-cert.shを見る
#!/bin/bash
mkdir cert && cd cert
if [[ $? != 0 ]]; then
exit
fi
echo -n "Hostname of Node1: "
read NODE1_HOSTNAME
echo -n "Hostname of Node2: "
read NODE2_HOSTNAME
echo -n "Hostname of Node3: "
read NODE3_HOSTNAME
echo -n "Addresses of Node1 (x.x.x.x[,x.x.x.x]): "
read NODE1_ADDRESS
echo -n "Addresses of Node2 (x.x.x.x[,x.x.x.x]): "
read NODE2_ADDRESS
echo -n "Addresses of Node3 (x.x.x.x[,x.x.x.x]): "
read NODE3_ADDRESS
echo -n "Address of Kubernetes ClusterIP (first address of ClusterIP subnet): "
read KUBERNETES_SVC_ADDRESS
cat > ca-config.json <<EOF
{
"signing": {
"default": {
"expiry": "8760h" # デフォルトの証明書の有効期間(1年)
},
"profiles": {
"kubernetes": {
"usages": ["signing", "key encipherment", "server auth", "client auth"],
"expiry": "8760h" # Kubernetesプロファイルの証明書有効期間
}
}
}
}
EOF
cat > ca-csr.json <<EOF
{
"CN": "Kubernetes", # 一般名(Common Name)
"key": {
"algo": "rsa", # 鍵のアルゴリズム(RSA)
"size": 2048 # 鍵のサイズ(2048ビット)
},
"names": [
{
"C": "JP", # 国(Country)
"L": "Setagaya", # 地域(Locality)
"O": "Kubernetes", # 組織名(Organization)
"OU": "CA", # 組織単位名(Organizational Unit)
"ST": "Chiba" # 州・県(State)
}
]
}
EOF
echo "---> Generate CA certificate"
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
cat > admin-csr.json <<EOF
{
"CN": "admin",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "JP",
"L": "Setagaya",
"O": "system:masters",
"OU": "Kubernetes The Hard Way",
"ST": "Chiba"
}
]
}
EOF
echo "---> Generate certificate for admin user"
cfssl gencert \
-ca=ca.pem \ # CAの証明書ファイル
-ca-key=ca-key.pem \ # CAの秘密鍵ファイル
-config=ca-config.json \ # CAの設定ファイル
-profile=kubernetes \ # 使用するプロファイル(kubernetes)
admin-csr.json | cfssljson -bare admin # 管理者用CSRファイルと出力ファイルの指定
for instance in ${NODE1_HOSTNAME} ${NODE2_HOSTNAME} ${NODE3_HOSTNAME}; do
cat > ${instance}-csr.json <<EOF
{
"CN": "system:node:${instance}",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "JP",
"L": "Setagaya",
"O": "system:nodes",
"OU": "Kubernetes The Hard Way",
"ST": "Chiba"
}
]
}
EOF
done
echo "---> Generate certificate for kubelet"
cfssl gencert \
-ca=ca.pem \ # CAの証明書ファイル
-ca-key=ca-key.pem \ # CAの秘密鍵ファイル
-config=ca-config.json \ # CAの設定ファイル
-hostname=${NODE1_HOSTNAME},${NODE1_ADDRESS} \ # 証明書に含めるホスト名とIPアドレス
-profile=kubernetes \ # 使用するプロファイル(kubernetes)
${NODE1_HOSTNAME}-csr.json | cfssljson -bare ${NODE1_HOSTNAME} # NODE1のCSRファイルと出力ファイルの指定
cfssl gencert \
-ca=ca.pem \
-ca-key=ca-key.pem \
-config=ca-config.json \
-hostname=${NODE2_HOSTNAME},${NODE2_ADDRESS} \
-profile=kubernetes \
${NODE2_HOSTNAME}-csr.json | cfssljson -bare ${NODE2_HOSTNAME}
cfssl gencert \
-ca=ca.pem \
-ca-key=ca-key.pem \
-config=ca-config.json \
-hostname=${NODE3_HOSTNAME},${NODE3_ADDRESS} \
-profile=kubernetes \
${NODE3_HOSTNAME}-csr.json | cfssljson -bare ${NODE3_HOSTNAME}
cat > kube-controller-manager-csr.json <<EOF
{
"CN": "system:kube-controller-manager",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "JP",
"L": "Setagaya",
"O": "system:kube-controller-manager",
"OU": "Kubernetes The Hard Way",
"ST": "Tokyo"
}
]
}
EOF
echo "---> Generate certificate for kube-controller-manager"
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
cat > kube-proxy-csr.json <<EOF
{
"CN": "system:kube-proxy",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "JP",
"L": "Setagaya",
"O": "system:node-proxier",
"OU": "Kubernetes The Hard Way",
"ST": "Tokyo"
}
]
}
EOF
echo "---> Generate certificate for kube-proxy"
cfssl gencert \
-ca=ca.pem \
-ca-key=ca-key.pem \
-config=ca-config.json \
-profile=kubernetes \
kube-proxy-csr.json | cfssljson -bare kube-proxy
cat > kube-scheduler-csr.json <<EOF
{
"CN": "system:kube-scheduler",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "JP",
"L": "Setagaya",
"O": "system:kube-scheduler",
"OU": "Kubernetes The Hard Way",
"ST": "Tokyo"
}
]
}
EOF
echo "---> Generate certificate for kube-scheduler"
cfssl gencert \
-ca=ca.pem \
-ca-key=ca-key.pem \
-config=ca-config.json \
-profile=kubernetes \
kube-scheduler-csr.json | cfssljson -bare kube-scheduler
KUBERNETES_HOSTNAMES=kubernetes,kubernetes.default,kubernetes.default.svc,kubernetes.default.svc.cluster,kubernetes.default.svc.cluster.local
cat > kubernetes-csr.json <<EOF
{
"CN": "kubernetes",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "JP",
"L": "Setagaya",
"O": "Kubernetes",
"OU": "Kubernetes The Hard Way",
"ST": "Tokyo"
}
]
}
EOF
echo "---> Generate certificate for kube-api-server"
cfssl gencert \
-ca=ca.pem \ # CAの証明書ファイル
-ca-key=ca-key.pem \ # CAの秘密鍵ファイル
-config=ca-config.json \ # CAの設定ファイル
-hostname=${KUBERNETES_SVC_ADDRESS},${NODE1_ADDRESS},${NODE2_ADDRESS},${NODE3_ADDRESS},127.0.0.1,${KUBERNETES_HOSTNAMES} \ # 証明書に関連付けるホスト名とIPアドレス
-profile=kubernetes \ # 使用するプロファイル(kubernetes)
kubernetes-csr.json | cfssljson -bare kubernetes # Kubernetes APIサーバー用CSRファイルと出力ファイルの指定
cat > service-account-csr.json <<EOF
{
"CN": "service-accounts",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "JP",
"L": "Setagaya",
"O": "Kubernetes",
"OU": "Kubernetes The Hard Way",
"ST": "Tokyo"
}
]
}
EOF
echo "---> Generate certificate for generating token of ServiceAccount"
cfssl gencert \
-ca=ca.pem \
-ca-key=ca-key.pem \
-config=ca-config.json \
-profile=kubernetes \
service-account-csr.json | cfssljson -bare service-account
echo "---> Complete to generate certificate"
# 実行例
./generate-cert.sh
# Hostname of Node1: master1
# Hostname of Node2: worker1
# Hostname of Node3: worker2
# Addresses of Node1 (x.x.x.x[,x.x.x.x]): 192.168.0.5
# Addresses of Node2 (x.x.x.x[,x.x.x.x]): 192.168.0.7
# Addresses of Node3 (x.x.x.x[,x.x.x.x]): 192.168.0.8
# Address of Kubernetes ClusterIP (first address of ClusterIP subnet): 10.32.0.1
# ...
# ...
# ---> Complete to generate certificate
作成したca.pem
と各hostnameに対応した<hostname>.pem
と<hostname>-key.pem
はそれぞれのホストにscp
コマンドで送ります。
# 例
scp ca.pem worker1.pem worker1-key.pem ubuntu@192.168.0.7:~/
これらはkubeletがkube-apiserverを認証したり、kube-apiserverがkubeletを認証するために使います。残りの証明書はmaster1で使用します。
kubeconfigの作成
各コンポーネント(kubelet, kube-proxy, kube-controller-manager, kube-scheduler, kubectlコマンド)が、kube-apiserverと通信するための設定ファイルを作成します。
設定ファイルを作成するためにkubectlコマンドを使用するので、まずはkubectlのバイナリをインストールします。
curl -LO https://dl.k8s.io/release/v1.29.0/bin/linux/arm64/kubectl
chmod +x kubectl
sudo mv kubectl /usr/local/bin # kubectlのパスが通るようにする
そして以下の設定ファイル作成用シェルスクリプトをgenerate-kubeconfig.sh
として作成し、実行権限を与えて実行します。内容は、各コンポーネント用の証明書情報をkubeconfigに組み込み、kube-apiserverのアドレスを指定するというものです。
generate-kubeconfig.shを見る
#!/bin/bash
ls cert >/dev/null 2>&1
if [[ $? != 0 ]]; then
echo "Please run in the same directory as cert"
exit
fi
mkdir kubeconfig && cd kubeconfig
if [[ $? != 0 ]]; then
exit
fi
CERT_DIR="../cert"
echo -n "Hostname of Node1: "
read NODE1_HOSTNAME
echo -n "Hostname of Node2: "
read NODE2_HOSTNAME
echo -n "Hostname of Node3: "
read NODE3_HOSTNAME
echo -n "Address of Master Node: "
read MASTER_ADDRESS
echo "---> Generate kubelet kubeconfig"
for instance in ${NODE1_HOSTNAME} ${NODE2_HOSTNAME} ${NODE3_HOSTNAME}; do
kubectl config set-cluster kubernetes-the-hard-way \
--certificate-authority=${CERT_DIR}/ca.pem \ # CAの証明書ファイル
--embed-certs=true \ # 証明書をkubeconfigに埋め込む
--server=https://${MASTER_ADDRESS}:6443 \ # Kubernetes APIサーバのアドレス
--kubeconfig=${instance}.kubeconfig
kubectl config set-credentials system:node:${instance} \
--client-certificate=${CERT_DIR}/${instance}.pem \ # クライアント証明書ファイル
--client-key=${CERT_DIR}/${instance}-key.pem \ # クライアント秘密鍵ファイル
--embed-certs=true \ # 証明書をkubeconfigに埋め込む
--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
echo "---> Generate kube-proxy kubeconfig"
kubectl config set-cluster kubernetes-the-hard-way \
--certificate-authority=${CERT_DIR}/ca.pem \
--embed-certs=true \
--server=https://${MASTER_ADDRESS}:6443 \
--kubeconfig=kube-proxy.kubeconfig
kubectl config set-credentials system:kube-proxy \
--client-certificate=${CERT_DIR}/kube-proxy.pem \
--client-key=${CERT_DIR}/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
echo "---> Generate kube-controller-manager kubeconfig"
kubectl config set-cluster kubernetes-the-hard-way \
--certificate-authority=${CERT_DIR}/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=${CERT_DIR}/kube-controller-manager.pem \
--client-key=${CERT_DIR}/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
echo "---> Generate kube-scheduler kubeconfig"
kubectl config set-cluster kubernetes-the-hard-way \
--certificate-authority=${CERT_DIR}/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=${CERT_DIR}/kube-scheduler.pem \
--client-key=${CERT_DIR}/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
echo "---> Generate admin user kubeconfig"
kubectl config set-cluster kubernetes-the-hard-way \
--certificate-authority=${CERT_DIR}/ca.pem \
--embed-certs=true \
--server=https://127.0.0.1:6443 \
--kubeconfig=admin.kubeconfig
kubectl config set-credentials admin \
--client-certificate=${CERT_DIR}/admin.pem \
--client-key=${CERT_DIR}/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
echo "---> Complete to generate kubeconfig"
chmod +x generate-kubeconfig.sh
./generate-kubeconfig.sh
# Hostname of Node1: master1
# Hostname of Node2: worker1
# Hostname of Node3: worker2
# Address of Master Node: 192.168.0.5
# ...
# ...
# ---> Complete to generate kubeconfig
作成した<hostname>.kubeconfig
とkube-proxy.kubeconfig
はそれぞれのホストにscp
で送ります。
etcdのデプロイ(Master)
このステップはマスターノードのみで行います。
まずetcdのバイナリをインストールしてパスが通るようにします。
curl -LO https://github.com/etcd-io/etcd/releases/download/v3.5.11/etcd-v3.5.11-linux-arm64.tar.gz
tar -xvf etcd-v3.5.11-linux-arm64.tar.gz
sudo mv etcd-v3.5.11-linux-arm64/etcd* /usr/local/bin/
etcd用の設定ファイルや証明書の配置を行うディレクトリを作成して配置します。これらのパスは後でsystemd用のユニットファイルを作成する際に指定します。
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/
etcdを動かすためのsystemd用のユニットファイルを作成します。ETCD_NAME
は、マスターノードが複数台ある場合に各マスターノードのetcdが同期するときに使用する名前です。私はmaster1としました。
ETCD_NAME="<etcd_name>"
INTERNAL_IP="<master_ip>"
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} \\ # このetcdインスタンスの名前
--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 \\ # CA証明書ファイル
--peer-trusted-ca-file=/etc/etcd/ca.pem \\ # ピア通信用のCA証明書ファイル
--peer-client-cert-auth \\ # ピア通信の証明書認証を要求
--client-cert-auth \\ # クライアント証明書認証を有効化
--initial-advertise-peer-urls https://${INTERNAL_IP}:2380 \\ # 初期ピアURLの公開先を指定
--listen-peer-urls https://${INTERNAL_IP}:2380 \\ # ピア通信のリッスンURL
--listen-client-urls https://${INTERNAL_IP}:2379,https://127.0.0.1:2379 \\ # クライアント通信のリッスンURL
--advertise-client-urls https://${INTERNAL_IP}:2379 \\ # クライアントURLの公開先を指定
--initial-cluster-token etcd-initial-token \\ # クラスタトークン
--initial-cluster ${ETCD_NAME}=https://${INTERNAL_IP}:2380 \\ # 初期クラスタ構成
--initial-cluster-state new \\ # クラスタの初期状態
--data-dir=/var/lib/etcd # データディレクトリ
Restart=on-failure
RestartSec=5
Environment=ETCD_UNSUPPORTED_ARCH=arm64
[Install]
WantedBy=multi-user.target
EOF
ユニットファイルで記述されている各オプションについての説明や、その他オプションについてはetcd公式ドキュメントから確認できます。
これをset-etcd.sh
として作成し、実行権限を与え実行します。
chmod +x set-etcd.sh
./set-etcd.sh
次にetcdをデーモンとして動くように以下のコマンドを実行します
sudo systemctl daemon-reload
sudo systemctl start etcd
以下のコマンドを実行して、Active: active
になっていればOKです
systemctl status etcd
#● etcd.service - etcd
# Loaded: loaded (/etc/systemd/system/etcd.service; disabled; vendor preset: enabled)
# Active: active (running) since Sun 2023-12-31 08:26:28 UTC; 2s ago
kube-apiserverのデプロイ(Master)
このステップもマスターノードのみで行います。
まずkube-apiserverのバイナリをインストールしてパスが通るようにします。
curl -LO curl -LO https://dl.k8s.io/release/v1.29.0/bin/linux/arm64/kube-apiserver
chmod +x kube-apiserver
sudo mv kube-apiserver /usr/local/bin/
次にetcdのデータを暗号化する機能のための設定ファイルを作成します。このステップは必須ではありませんが、必須レベルなので行います。
encryption configurationに関するドキュメントはこちらです
# 32バイトのランダムデータを生成し、Base64エンコードして暗号化キーを作成
ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)
# 暗号化設定ファイルを作成
cat > encryption-config.yaml <<EOF
kind: EncryptionConfiguration
apiVersion: apiserver.config.k8s.io/v1
resources:
- resources:
- secrets # 暗号化対象のリソース(ここではsecrets)
providers:
- aescbc: # AES-CBCモードでの暗号化を使用
keys:
- name: key1
secret: ${ENCRYPTION_KEY} # 生成した暗号化キー
- identity: {} # 後退プロバイダとしてidentityを指定
EOF
kube-apiserver用の設定ファイルや証明書の配置を行うディレクトリを作成して配置します。これらのパスは後でsystemd用のユニットファイルで指定します。
-ai
オプションについては、証明書ファイルの様々な情報を保持しながらコピーするために使用します。
sudo mkdir -p /etc/kubernetes/config
sudo mkdir -p /var/lib/kubernetes/
sudo cp -ai ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem \
service-account-key.pem service-account.pem encryption-config.yaml /var/lib/kubernetes/
kube-apiserverをデーモンとして動かすためのユニットファイルを作成し、起動します。
INTERNAL_IP="<master_ip>"
CLUSTER_IP_NETWORK="<cluster_ip_network>"
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} \\ # クラスタ内で使用するAPIサーバーのIPアドレスを指定
--allow-privileged=true \\ # 特権コンテナの使用を許可
--apiserver-count=3 \\ # クラスタ内のAPIサーバの数
--audit-log-maxage=30 \\ # 監査ログの保持日数
--audit-log-maxbackup=3 \\ # 監査ログの最大バックアップ数
--audit-log-maxsize=100 \\ # 監査ログファイルの最大サイズ(MB)
--audit-log-path=/var/log/audit.log \\ # 監査ログの保存先
--authorization-mode=Node,RBAC \\ # 認証モード(NodeとRBAC)
--bind-address=0.0.0.0 \\ # APIサーバがリッスンするIPアドレス
--client-ca-file=/var/lib/kubernetes/ca.pem \\ # クライアント証明書認証用のCA証明書ファイル
--enable-admission-plugins=NamespaceLifecycle,NodeRestriction,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota \\ # 使用するアドミッションコントローラー
--etcd-cafile=/var/lib/kubernetes/ca.pem \\ # etcdのCA証明書ファイル
--etcd-certfile=/var/lib/kubernetes/kubernetes.pem \\ # etcdのクライアント証明書ファイル
--etcd-keyfile=/var/lib/kubernetes/kubernetes-key.pem \\ # etcdのクライアント鍵ファイル
--etcd-servers=https://${INTERNAL_IP}:2379 \\ # etcdサーバのURL
--event-ttl=1h \\ # イベントのTTL
--encryption-provider-config=/var/lib/kubernetes/encryption-config.yaml \\ # 暗号化プロバイダの設定ファイル
--kubelet-certificate-authority=/var/lib/kubernetes/ca.pem \\ # kubeletの証明書認証用CA
--kubelet-client-certificate=/var/lib/kubernetes/kubernetes.pem \\ # kubeletのクライアント証明書
--kubelet-client-key=/var/lib/kubernetes/kubernetes-key.pem \\ # kubeletのクライアント鍵
--kubelet-https=true \\ # kubeletとの通信にHTTPSを使用
--runtime-config='api/all=true' \\ # APIのランタイム設定
--service-account-key-file=/var/lib/kubernetes/service-account.pem \\ # サービスアカウントの鍵ファイル
--service-cluster-ip-range=${CLUSTER_IP_NETWORK} \\ # サービスクラスタのIP範囲
--service-node-port-range=30000-32767 \\ # ノードポートの範囲
--tls-cert-file=/var/lib/kubernetes/kubernetes.pem \\ # TLS証明書ファイル
--tls-private-key-file=/var/lib/kubernetes/kubernetes-key.pem \\ # TLS秘密鍵ファイル
--v=2 # ロギングの詳細度レベル
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl start kube-apiserver
kube-apiserverの実行オプションについてはこちらの公式ドキュメントから確認できます
kube-controller-managerのデプロイ(Master)
これもマスターノードのみで行います。
まずkube-controller-managerのバイナリをインストールしてパスが通るようにします。
curl -LO curl -LO https://dl.k8s.io/release/v1.29.0/bin/linux/arm64/kube-controller-manager
chmod +x kube-controller-manager
sudo mv kube-controller-manager /usr/local/bin/
kubeconfigの配置を行います
sudo cp -ai kube-controller-manager.kubeconfig /var/lib/kubernetes/
kube-controller-managerをデーモンとして動かすためのユニットファイルを作成し、起動します。
私はpod_network
に10.10.0.0/16
, cluster_ip_network
に10.32.0.0/24
を指定しました。
POD_NETWORK="<pod_network>"
CLUSTER_IP_NETWORK="<cluster_ip_network>"
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 \\ # コントローラーマネージャがリッスンするIPアドレス
--cluster-cidr=${POD_NETWORK} \\ # PodネットワークのCIDR範囲
--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 \\ # 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=${CLUSTER_IP_NETWORK} \\ # サービスクラスタIP範囲
--use-service-account-credentials=true \\ # サービスアカウントの認証情報を使用
--v=2 # ロギングの詳細度レベル
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl start kube-controller-manager
kube-controller-managerについてのオプションについてはこちらの公式ドキュメントから確認できます。
kube-schedulerのデプロイ(Master)
これもマスターノードのみで行います。
まずkube-schedulerのバイナリをインストールしてパスが通るようにします。
curl -LO curl -LO https://dl.k8s.io/release/v1.29.0/bin/linux/arm64/kube-scheduler
chmod +x kube-scheduler
sudo mv kube-scheduler /usr/local/bin/
kubeconfigの配置を行います。
sudo cp -ai kube-scheduler.kubeconfig /var/lib/kubernetes/
kube-schedulerをデーモンとして動かすためのユニットファイルを作成し、起動します。
cat <<EOF | sudo tee /etc/kubernetes/config/kube-scheduler.yaml
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
clientConnection:
kubeconfig: "/var/lib/kubernetes/kube-scheduler.kubeconfig" # Kube-Scheduler用のkubeconfigファイルのパス
leaderElection:
leaderElect: true # リーダー選出を有効化
EOF
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 \\ # 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 start kube-scheduler
kube-schedulerの実行オプションについてはこちらの公式ドキュメントから確認できます。
Masterの動作チェック
Masterの各コンポーネント(scheduler, controller-manager, etcd)が動いているかを確認します。
# 実行例(成功)
kubectl get --raw='/readyz?verbose'
# [+]ping ok
# [+]log ok
# [+]etcd ok
# [+]poststarthook/start-kube-apiserver-admission-initializer ok
# [+]poststarthook/generic-apiserver-start-informers ok
# [+]poststarthook/start-apiextensions-informers ok
# [+]poststarthook/start-apiextensions-controllers ok
# [+]poststarthook/crd-informer-synced ok
# [+]poststarthook/bootstrap-controller ok
# [+]poststarthook/rbac/bootstrap-roles ok
# [+]poststarthook/scheduling/bootstrap-system-priority-classes ok
# [+]poststarthook/start-cluster-authentication-info-controller ok
# [+]poststarthook/start-kube-aggregator-informers ok
# [+]poststarthook/apiservice-registration-controller ok
# [+]poststarthook/apiservice-status-available-controller ok
# [+]poststarthook/kube-apiserver-autoregistration ok
# [+]autoregister-completion ok
# [+]poststarthook/apiservice-openapi-controller ok
非推奨になったものですが、こちらでも一応確認できます
ubuntu@master1:~$ kubectl get componentstatuses --kubeconfig kubeconfig/admin.kubeconfig
# NAME STATUS MESSAGE ERROR
# scheduler Healthy ok
# controller-manager Healthy ok
# etcd-0 Healthy ok
もし動いていない場合は、journalctl
等のコマンドを実行し、エラーになっている箇所などを調べます。バージョンの変化により撤廃されたオプションをユニットファイルやconfigファイルで指定しているなどがエラーの原因になります。
journalctl -u <component> -r
Kubernetes Node構築の前準備(Node)
これからはワーカーノードで作業を行っていきます。
まずノードのcgroupのMemory Subsystemを有効化します。デフォルトでは無効化されているので有効にする必要があります。vim
等を使用し、/boot/firmware/cmdline.txt
に下記を追記して再起動します。
cgroup_memory=1 cgroup_enable=memory
Kubernetesの内部で使われているパッケージをインストールします。
sudo apt update
sudo apt -y install socat conntrack ipset
kubeletのデプロイ(Node)
kubelet用の設定ファイルや証明書の配置を行うディレクトリを作成して配置します。
これらのパスは後でsystemd用のユニットファイルで指定します。
sudo mkdir -p \
/etc/cni/net.d \
/opt/cni/bin \
/var/lib/kubelet \
/var/lib/kubernetes \
/etc/containerd
sudo cp -ai ${HOSTNAME}-key.pem ${HOSTNAME}.pem /var/lib/kubelet/
sudo cp -ai ${HOSTNAME}.kubeconfig /var/lib/kubelet/kubeconfig
sudo cp -ai ca.pem /var/lib/kubernetes/
コンテナ作成に必要なツールやcniプラグイン, kubeletをインストールします。cniプラグインにはflannelやcalicoではなく、最低限の機能を持ったプラグインを使用します。これはkubernetes the hard wayに従っています。
curl -LO https://dl.k8s.io/release/v1.29.0/bin/linux/arm64/kubelet
curl -LO https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.29.0/crictl-v1.29.0-linux-arm64.tar.gz
curl -LO https://github.com/containernetworking/plugins/releases/download/v1.4.0/cni-plugins-linux-arm64-v1.4.0.tgz
tar -xvf crictl-v1.29.0-linux-arm64.tar.gz
sudo tar -xvf cni-plugins-linux-arm64-v1.4.0.tgz -C /opt/cni/bin/
chmod +x crictl kubelet
sudo mv crictl kubelet /usr/local/bin/
sudo apt -y install containerd runc
cniプラグインを動作させるためにPodネットワークの設定をします
POD_CIDR
は、先述の設定において、worker1であれば10.10.1.0/24
, worker2であれば10.10.2.0/24
を指定するものです。
# POD_CIDR変数を設定(ノード固有のPodネットワークCIDRを指定)
POD_CIDR=<node_pod_cidr>
# ブリッジネットワークのCNI設定ファイルを作成
cat <<EOF | sudo tee /etc/cni/net.d/10-bridge.conf
{
"cniVersion": "0.3.1", # CNIのバージョン
"name": "bridge", # ネットワーク名
"type": "bridge", # ネットワークタイプ(ブリッジ)
"bridge": "cnio0", # 使用するブリッジインターフェース名
"isGateway": true, # ゲートウェイとして機能
"ipMasq": true, # IPマスカレードを有効化(NAT)
"ipam": { # IPアドレス管理(IPAM)の設定
"type": "host-local", # ローカルホストでのIPアドレス管理
"ranges": [
[{"subnet": "${POD_CIDR}"}] # Podに割り当てるIP範囲
],
"routes": [{"dst": "0.0.0.0/0"}] # ルーティング設定
}
}
EOF
# ループバックネットワークのCNI設定ファイルを作成
cat <<EOF | sudo tee /etc/cni/net.d/99-loopback.conf
{
"cniVersion": "0.3.1", # CNIのバージョン
"name": "lo", # ネットワーク名(ループバック)
"type": "loopback" # ネットワークタイプ(ループバック)
}
EOF
kubeletをデーモンとして動かすためのユニットファイルを作成し、起動します
POD_CIDR=<node_pod_cidr>
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 # Webhookによる認証を有効化
x509:
clientCAFile: "/var/lib/kubernetes/ca.pem" # x509認証用のCA証明書
authorization:
mode: Webhook # 認可はWebhookを使用
clusterDomain: "cluster.local" # クラスタのドメイン
clusterDNS:
- "10.32.0.10" # クラスタのDNSサーバアドレス
podCIDR: "${POD_CIDR}" # このノードが使用するPodのCIDR
resolvConf: "/run/systemd/resolve/resolv.conf" # DNS解決用の設定ファイル
runtimeRequestTimeout: "15m" # ランタイムリクエストのタイムアウト時間
tlsCertFile: "/var/lib/kubelet/${HOSTNAME}.pem" # TLS証明書ファイル
tlsPrivateKeyFile: "/var/lib/kubelet/${HOSTNAME}-key.pem" # TLS秘密鍵ファイル
EOF
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 \\ # Kubeletの設定ファイル
--container-runtime-endpoint=unix:///var/run/containerd/containerd.sock \\ # コンテナランタイムのエンドポイント
--kubeconfig=/var/lib/kubelet/kubeconfig \\ # Kubeletのkubeconfigファイル
--register-node=true \\ # ノードを自動でクラスタに登録
--v=2 # ロギングの詳細度レベル
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
kubeletのconfigurationについては以下の公式ドキュメントから確認できます。
kubeletの実行オプションについては以下の公式ドキュメントから確認できます。kube-proxyのデプロイ(Node)
kube-proxyのバイナリをインストールします。
curl -LO https://dl.k8s.io/release/v1.29.0/bin/linux/arm64/kube-proxy
chmod +x kube-proxy
sudo mv kube-proxy /usr/local/bin/
kube-proxy用の設定ファイルや証明書の配置を行うディレクトリを作成して配置します。
sudo mkdir -p /var/lib/kube-proxy
sudo mv kube-proxy.kubeconfig /var/lib/kube-proxy/kubeconfig
kube-proxyをデーモンとして動かすためのユニットファイルを作成し、起動します。
# POD_NETWORK変数を設定(PodネットワークのCIDRを指定)
POD_NETWORK="<pod_network>"
# Kube-Proxyの設定ファイルを作成
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" # Kube-Proxy用のkubeconfigファイルのパス
mode: "iptables" # ネットワークトラフィックを処理するモード(iptables)
clusterCIDR: "${POD_NETWORK}" # クラスタ内のPodネットワークのCIDR
EOF
# Kube-Proxyのsystemdサービスユニットを作成
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 \\ # Kube-Proxyの実行コマンド
--config=/var/lib/kube-proxy/kube-proxy-config.yaml # 設定ファイルのパス
Restart=on-failure # 失敗時に再起動
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
kube-proxyのconfigurationについては以下の公式ドキュメントから確認できます。
kube-proxyの実行オプションについては以下の公式ドキュメントから確認できます。ルーティング設定の追加
今回はiptablesを利用したPodネットワークであるため、Nodeは自身以外のPodネットワークを知りません。そこで他のNodeへのルーティング情報を与えてあげます。
# 特定のサブネットへのルーティングを設定
# master1において実行
# 10.10.1.0/24のサブネット宛のパケットは192.168.0.7を経由してルーティングされる
sudo ip route add 10.10.1.0/24 via 192.168.0.7 dev eth0
# 10.10.2.0/24のサブネット宛のパケットは192.168.0.8を経由してルーティングされる
sudo ip route add 10.10.2.0/24 via 192.168.0.8 dev eth0
# worker1において実行
sudo ip route add 10.10.2.0/24 via 192.168.08 dev eth0
# worker2において実行
sudo ip route add 10.10.1.0/24 via 192.168.07 dev eth0
Nodeの動作チェック
Nodeのコンポーネントをsystemctl
コマンドで起動し終えたら、kube-apiserverに認識されているかどうかを確認します
ubuntu@master1:~$ k get node
# NAME STATUS ROLES AGE VERSION
# worker1 Ready <none> 3d20h v1.29.0
# worker2 Ready <none> 3d19h v1.29.0
このようになったら、各ノードがkubernetesクラスタに参加できているということになります。
nginxのデプロイチェック
Podが起動できることを確認します。試しにnginxをデプロイしてみます。
kubectl create deployment nginx --image=nginx
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# nginx-f89759699-t9vkd 1/1 Running 0 61s
ここでkubectl
コマンドの実行がうまくいかないときは次のようにconfigファイルを直接指定してみてください
kubectl get pods --kubeconfig admin.kubeconfig
実際にアクセスできるかを確認します
ServiceリソースのNodePortをデプロイして、外部に公開できるようにします。
kubectl expose deployment nginx --type=NodePort --port=80
kubectl get service nginx
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# kubernetes ClusterIP 10.32.0.1 <none> 443/TCP 44m
# nginx NodePort 10.32.0.77 <none> 80:32105/TCP 33s
このように出力されたら、curlでアクセスしてみます。
curl http://192.168.0.7:32105
これでhtmlが取得できていれば成功です。これで最低限のKubernetesクラスタを構築することができました。
最後に
Kubernetesクラスタを構築することはできましたでしょうか。もしできないという方は、CyberAgentさんのリポジトリや、kubernetes-the-hard-way 日本語も参考にしてみてください。
本記事を読んで頂きありがとうございました。
参考にしたリンク集
Discussion