docker
kubernetes
centos7
k8s
kubeadm

kubeadmを使ってオンプレミスのプロキシ環境下にKubernetesクラスタをデプロイする(CentOS 7.3)

クラスタのデプロイ・管理が大変なのでほんとはk8sのマネージドサービス使いたい...GKEとか。
でもオンプレミスで運用せざるを得ないことってありますよね。しかもプロキシ環境下で。

そんな状況下でk8sクラスタを構築する方法の備忘録です。

kubeadm

公式が提供しているk8sクラスタを構築するためのツールです。現在のバージョンはまだBeta版ですが、2018年内にGA予定のようです。

この記事の内容は基本的には公式ドキュメントの焼き直し+α(プロキシ設定など)です。

Beta版ということもあり、クラスタデータを保持するetcdの冗長化、およびMasterコンポーネントが複数存在するようなHigh Availability構成のクラスタは単純にkubeadmを使うだけでは構築できません。
今回はHA構成までは考えず、今回利用したkubeadm v1.9がカバーする範囲である単一Master構成のクラスタを構築します。

このあたりはGA時点では対応されるんでしょうか。

環境

  • CentOS 7.3(master, worker)
  • プロキシ経由でインターネットに接続可能な環境

k8sクラスタ構成

  • master: 単一構成
  • podネットワーク: flannel
  • コンテナランタイム: docker

バージョン

  • kubernetes v1.9.0
  • kubeadm v1.9.0
  • docker v1.12.6
  • flannel v0.9.1

準備

kubeadmでk8sクラスタを構築するための準備です。各master/worker nodeすべてで実施します。rootで実施する前提です。

/etc/hostsの設定

以下のように新しい行にホスト名に対応するIPアドレスを追記します。

[ip_address] [hostname]

以下のコマンド実行で追記できます(単一NIC、イーサネットの前提です)。

echo $(ip a | grep 'en\|eth0' | grep "inet" | cut -d' ' -f6 | cut -d/ -f1) $(hostname) >> /etc/hosts

yumのプロキシ設定

kubeadmはじめdocker, kubeletなどのk8sの動作に必要なパッケージはyumでインストールします。
プロキシ経由でyumが利用できるよう/etc/yum.confに以下を設定しておきます。

proxy = http://[proxy_host]:[proxy_port]

以下のコマンド実行で設定できます。

echo proxy = http://[proxy_host]:[proxy_port] >> /etc/yum.conf
  • プロキシで認証が必要な場合は以下も追加します。

    echo proxy_username = [proxy_user] >> /etc/yum.conf
    echo proxy_password = [proxy_pass] >> /etc/yum.conf
    

firewall無効化

systemctl disable firewalld
systemctl stop firewalld

SELinux無効化

setenforce 0

/etc/selinux/configを編集しdisableにしておきます。

SELINUX=disabled

以下のコマンドでdisableに変更できます。

sed -e "/^SELINUX=enforcing$/s/SELINUX=enforcing/SELINUX=disabled/" -i.bak /etc/selinux/config

MACアドレスおよびproduct_uuidがすべてのノード間で重複していないことの確認

公式ドキュメントに

Unique hostname, MAC address, and product_uuid for every node

とあるので確認しておきます。

  • MACアドレス確認

    ip link
    
  • product_uuid確認

    cat /sys/class/dmi/id/product_uuid
    

swapを無効化

これも公式ドキュメントに

Swap disabled. You MUST disable swap in order for the kubelet to work properly.

とあるので無効にしておきます。

swapoff -a
sed -e "/^UUID=[a-z0-9-]* swap/s/^/# /" -i.bak /etc/fstab
cat /etc/fstab

Kubernetesコンポーネントが使用するポートが他プロセスで使用されていないか確認

k8sの各コンポーネントが利用するポートが使用されていないことを確認します。

  • Master node

    Protocol Direction Port Range Purpose
    TCP Inbound 6443* Kubernetes API server
    TCP Inbound 2379-2380 etcd server client API
    TCP Inbound 10250 Kubelet API
    TCP Inbound 10251 kube-scheduler
    TCP Inbound 10252 kube-controller-manager
    TCP Inbound 10255 Read-only Kubelet API
  • Worker nodes

    Protocol Direction Port Range Purpose
    TCP Inbound 10250 Kubelet API
    TCP Inbound 10255 Read-only Kubelet API
    TCP Inbound 30000-32767 NodePort Services*

*部分は設定により変更可能です。今回はk8s master/node上でk8s管理外のアプリケーションプロセスが動作することは想定していないため、デフォルト設定とします。

docker v1.12.6インストール

Kubernetesではv1.12が推奨(1.13や17.03でも動作確認済みとのこと)されているため、 v1.12.6をインストールします。

dockerインストール手順もほぼ公式ドキュメントの焼き直しです。

必要なyumリポジトリの有効化

yum update -y
yum install -y yum-utils
yum-config-manager --add-repo https://docs.docker.com/v1.13/engine/installation/linux/repo_files/centos/docker.repo
yum makecache fast

dockerパッケージのインストール

yum install -y docker-engine-1.12.6-1.el7.centos

dockerのproxy設定

Docker Hubなどインターネット上のコンテナイメージリポジトリからイメージを取得するため、プロキシ経由で接続可能な設定をする必要があります。

mkdir -p /etc/systemd/system/docker.service.d
cat <<EOF > /etc/systemd/system/docker.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://[proxy_user:proxy_pass@][proxy_host]:[proxy_port]" "HTTPS_PROXY=http://[proxy_user:proxy_pass@][proxy_host]:[proxy_port]" "NO_PROXY=localhost,127.0.0.1,[master_node_ip],[worker_nodes_ip],10.96.0.0/12,10.244.0.0/16"
EOF

NO_PROXYにはすべてのk8s master/worker nodeのIPアドレスを指定します。また、k8sの各Serviceに割り振られる仮想IP(10.96.0.0/12)、および後述するpodネットワークプラグインが展開するOverlayネットワークのIPアドレス範囲(10.244.0.0/16)をCIDRで記載しておきます。

これをしないとkubeadmでクラスタ構築するときに、preflightチェックで異なるホスト間のPodおよびServiceの通信がプロキシ経由でルーティングされちゃうよ!という旨のWarningが出力され、実際にうまく通信してくれませんでした。

proxy設定の反映

systemctl daemon-reload

dockerを起動&自動起動設定

systemctl enable docker && systemctl start docker

proxy設定が反映されていることを確認

$ docker info | grep Proxy
Http Proxy: http://[proxy_host]:[proxy_port]
Https Proxy: http://[proxy_host]:[proxy_port]
No Proxy: localhost,127.0.0.1,[master_node_ip],[worker_nodes_ip],10.96.0.0/12,10.244.0.0/16

dockerの動作確認

docker単体で動作可能なことを確認します。確認できたらもう利用しないのでイメージは消しておきます。

docker run --rm hello-world
docker rmi docker.io/library/hello-world

一般ユーザからdockerを利用可能にする

groupadd docker
usermod -aG docker [user_name]
systemctl restart docker

ログアウト, 再ログインを実施し設定を反映します。

ここまででdockerのインストールが完了しました。
dockerはコンテナイメージの取得、コンテナの起動、削除をこれから構築するk8sクラスタからの指示で行うことになります。

本番環境で利用する場合の設定(ストレージドライバ)

CentOS/RHELではdockerコンテナ/イメージごとのストレージを変更差分ごとに管理するため、デフォルトでdevicemapperというストレージドライバを利用します。

devicemapperはデフォルト設定の場合loop-lvmというモードで動作しています。このモードはコンテナ利用時のディスクI/Oのパフォーマンスが良くないので、プロダクション環境で運用する場合はdirect-lvmモードに変更することを公式が推奨しています。

詳細は公式ドキュメントに記載がありますが、ホストマシン上にLVMとthin poolを作成する必要があります。docker v17.06以降だとdaemon.jsonに設定を記載すると自動で作ってくれるみたいですね。

kubeadmインストール、kubenetesクラスタの構築

kubadmのプロキシ設定

設定内容はdockerのプロキシ設定時と同様です。ホスト上の環境変数として指定します。

環境変数が常に有効となるように.bash_profileに追記し反映します。

cat <<EOF >> ~/.bash_profile
PROXY_PORT=[proxy_port]
PROXY_HOST=[proxy_host]
http_proxy=http://[proxy_user:proxy_pass@]\$PROXY_HOST:\$PROXY_PORT
HTTP_PROXY=\$http_proxy
https_proxy=\$http_proxy
HTTPS_PROXY=\$http_proxy
no_proxy="localhost,127.0.0.1,[master_node_ip],[worker_nodes_ip],10.96.0.0/12,10.244.0.0/16"
EOF

source ~/.bash_profile

kubeadm, kubelet, kubectlパッケージリポジトリを追加

kubeadmとk8sの動作に必要なパッケージをインストールするためのyumリポジトリを追加します。

以下、簡単な説明です。下記以外のk8sコンポーネントはkubeadmがコンテナとして起動してくれます。

  • kubelet: pod, コンテナを起動する役割を持つ、クラスタ内のすべてのホストマシンに存在するコンポーネント
  • kubectl: K8sクラスタと対話するためのCLIユーティリティ
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg  https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF

kubeadm, kubelet, kubectlパッケージインストール(v1.9.0)

yum install -y kubelet-1.9.0-0 kubeadm-1.9.0-0 kubectl-1.9.0-0

K8sの通信が正しくルーティングされるよう設定

k8sはiptablesを更新することでクラスタ内の各Pod間のルーティングを実現しています。

RHEL/CentOSの場合以下の設定をしないと通信が正しくルーティングされないようです。

cat <<EOF >  /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system

kubeletを一度起動し、Cgroup Driverがdockerとkubelet間で一致していることを確認

CentOSデフォルトだとkubeletはsystemd、Dockerはcgroupfsを使うはずなので、利用するドライバの指定はDockerインストール時に先に実施しておいてもいいです。

systemctl enable kubelet && systemctl start kubelet
cat /etc/systemd/system/kubelet.service.d/10-kubeadm.conf | grep KUBELET_CGROUP_ARG
docker info | grep Cgroup
systemctl stop kubelet

どちらもcgroup-driver=systemdとなっていればOK

  • 異なる場合
    以下を実施後再度確認します。

    cat << EOF > /etc/docker/daemon.json
    {
    "exec-opts": ["native.cgroupdriver=systemd"]
    }
    EOF"
    systemctl restart docker
    

masterでのみ実施する作業

masterの初期化

まずはdry-runしてみる

--kubernetes-versionでk8sのバージョンを指定しています。また、--apiserver-advertise-addressでmasterのIPアドレスを指定します。

kubeadmはworker node追加時の認証のためにトークンを払い出すのですが、利用期限が決まっておりデフォルトで24時間有効です。今回は検証用途であり、あとでworker nodeを追加したりする可能性があるため、--token-ttl 0を指定し認証トークンの利用期限を無期限にしています。

--pod-network-cidrにはプロキシ設定でも指定したOverlayネットワークのIPアドレス範囲(10.244.0.0/16)をCIDR形式で指定します。

kubeadm init --kubernetes-version 1.9.0 --apiserver-advertise-address=$(ip a | grep 'en\|eth0' | grep "inet" | cut -d' ' -f6 | cut -d/ -f1) --pod-network-cidr=10.244.0.0/16 --token-ttl 0 --dry-run

以下が出力されれば問題なく実行できているので、実際に初期化実行します。

[dryrun]?Finished dry-running successfully. Above are the resources that would be created.
初期化
kubeadm init --kubernetes-version 1.9.0 --apiserver-advertise-address=$(ip a | grep 'en\|eth0' | grep "inet" | cut -d' ' -f6 | cut -d/ -f1) --pod-network-cidr=10.244.0.0/16 --token-ttl 0

以下が出力されれば初期化に成功しています。kubeadm join以降の出力はworker nodeを追加する際に実行するコマンドです。

Your Kubernetes master has initialized successfully!

To start using your cluster, you need to run (as a regular user):

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  http://kubernetes.io/docs/admin/addons/

You can now join any number of machines by running the following on each node
as root:

  kubeadm join --token [token] [K8s_master_host]:[port] --discovery-token-ca-cert-hash sha256:[hash]

kube-apiserverと対話可能にする設定

初期化時に出力されていた以下のコマンドを実行します。

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

Podネットワークアドオンのデプロイ

kubeadmはOverlayネットワークを展開するところまでは面倒見てくれないので、別途Podネットワークプラグインをクラスタにデプロイします。これをしないとずっとmasterのステータスがNot Ready状態となりk8sが機能しません。

今回はflannelを利用します。
master初期化時の--pod-network-cidrやプロキシ設定で指定したIPアドレス範囲(10.244.0.0/16)はこのflannelが展開するOverlayネットワークで利用します。

kubectl apply -f  https://raw.githubusercontent.com/coreos/flannel/v0.9.1/Documentation/kube-flannel.yml

master上でkube-dns PodがRunningになっていることを確認

Podネットワークが動作しているとしばらくしてkube-dnsがRunning状態になるはずなので確認します。

$ kubectl get pods -n kube-system
NAME                                               READY     STATUS    RESTARTS   AGE
etcd-[k8s_master_host]                             1/1       Running   0          10m
kube-apiserver-[k8s_master_host]                   1/1       Running   0          10m
kube-controller-manager-[k8s_master_host]          1/1       Running   0          10m
kube-dns-6f4fd4bdf-b7kw9                           3/3       Running   0          10m
kube-flannel-ds-c2r9b                              1/1       Running   0          10m
kube-proxy-s9krr                                   1/1       Running   0          10m
kube-scheduler-[k8s_master_host]                   1/1       Running   0          10m

masterのstatusがReadyになっていることを確認

masterの状態がReadyとなり、k8sクラスタとして利用できる状態になっていることを確認します。

$ kubectl get nodes
NAME                       STATUS    ROLES     AGE       VERSION
[k8s_master_host]          Ready     master    10m       v1.9.0

masterでもPod起動がスケジュールされるようにmasterへのPod配置を制限するtaintを削除

デフォルトではmasterへPodが配置されないよう、taintという機能によりmasterへのPodスケジューリングが制限されています。

リソースが不足していたりmaster単体で動かす場合は、master nodeに設定されているtaintを削除します。

kubectl taint nodes --all node-role.kubernetes.io/master-

worker nodeでのみ実施する作業

worker nodeの追加

masterの初期化時に出力されたtoken, hash値を引数に指定します。

kubeadm join --token [token] [K8s_master_host]:6443 --discovery-token-ca-cert-hash sha256:[hash]

以下が出れば成功

Node join complete:
* Certificate signing request sent to master and response
  received.
* Kubelet informed of new secure connection details.

Run 'kubectl get nodes' on the master to see this machine join.

K8sクラスタにworker nodeが追加されReadyになっていることをmasterで確認

$ kubectl get nodes
NAME                       STATUS    ROLES     AGE       VERSION
[k8s_master_host]          Ready     master    10m       v1.9.0
[k8s_worker_host]          Ready     master    10m       v1.9.0

以上でオンプレミス、プロキシ環境下のホスト上でk8sクラスタを動作させることができました。

これでプロキシ環境下でもkubectl applyやhelmなどを利用してアプリケーションをクラスタ上に展開していくことができます。

参考サイト