はじめに
前回の記事でも書いたように、余ったPCパーツで新しくPCを組み立てました。何に使おうかなーとおもっていたのですが、ちょうどkubernetesを最近触っていて、自由にいじれるクラスタ欲しいなーと思い試してみることにしました*1。
昨今では気軽に試せるようにminikubeとかkind とかありますが、違うんです。開発環境使ってkubernetesの使い方を知りたいんじゃなくて、オンプレとかの本番環境は実際にはどう構築するんだろう?みたいなのを知りたかったんです。
- はじめに
- その前に: kubernetesってなんだよ?
- kubernetes のアーキテクチャ
- 今回やりたいこと
- kubernetesクラスターの構築
- 試しになにかデプロイしてみる
- さいごに
- 参考文献
その前に: kubernetesってなんだよ?
コンテナオーケストレーションツールと呼ばれる、コンテナの運用管理やスケーリングを助けるツールです。コンテナ化されたアプリケーションに対して、負荷が大きかったら複製したり、複数のアプリケーションをまとめてデプロイしたり、みたいなのを簡単にすることができます。
kubernetesではクラスタと呼ばれる、通常複数台からなるマシンの集合を用意して、そこにコンテナを乗せて、そのコンテナを管理します。
kubernetes のアーキテクチャ
公式サイト に行くと、こんな感じの図が出てきます。各コンポーネントに対する説明も載っているので、読んでみるのが良いかと思います。
今回やりたいこと
先述したminikubeやkindは擬似的なクラスターを作成して、いい感じにkubernetesが使える環境を提供してくれます。ただ、今回はこのクラスター自体を自分でセットアップして、kubernetesのクラスターとして使えるようにすることを目指します。クラスターと言いつつ、手元の自由に破壊できるPCは1台なので、1つのノードがコントロールプレーンでもありワーカーノードでもある†最強†*2のクラスターを作ります。この操作を通してkubernetesのアーキテクチャをより理解できたらいいな、という感じ。
kubernetesクラスターの構築
基本的には公式ドキュメントにならっていきます。 kubeadmのインストール | Kubernetes
事前準備
ぼくの環境は以下です。
[root@mattyan1053 ~]# cat /etc/redhat-release Rocky Linux release 8.8 (Green Obsidian)
インストールするのはkubernetes 1.27系でした。
kubernetesではswapを無効にしないといけないので無効((man systemd.swap
))にします。ちなみにどのコンポーネントがswapにNGを出しているかというと、kubeletらしいです(kubeletの設定を変えればswap有効でもいけるらしいですが、自分の環境はメモリが16GBと潤沢にあるので無効にします)。
[root@mattyan1053 ~]# vi /etc/fstab # swapの行(おそらく末尾行)を削除 [root@mattyan1053 ~]# systemctl mask "dev-dm-1.swap" Unit dev-dm-1.swap does not exist, proceeding anyway. Created symlink /etc/systemd/system/dev-dm-1.swap → /dev/null. [root@mattyan1053 ~]# reboot [root@mattyan1053 ~]# swapon -s [root@mattyan1053 ~]# systemctl --type swap 0 loaded units listed. Pass --all to see loaded but inactive units, too. To show all installed unit files use 'systemctl list-unit-files'.
特定のポートがあいていれば実は大丈夫なんですが*3、その後もNodePortとか設定して個別に開けたり面倒なので(kuernetesはnftablesに互換性はないようです)、firewalldまたはnftablesを止めます。*4。自分の環境では元々firewalldが動いていて、iptablesは使っていませんでした。
[root@mattyan1053 ~]# rpm -qa | grep -E "firewalld|iptables|nftables" firewalld-filesystem-0.9.3-13.el8.noarch iptables-1.8.4-24.el8.x86_64 python3-nftables-0.9.3-26.el8.x86_64 iptables-libs-1.8.4-24.el8.x86_64 firewalld-0.9.3-13.el8.noarch iptables-ebtables-1.8.4-24.el8.x86_64 nftables-0.9.3-26.el8.x86_64 [root@mattyan1053 ~]# systemctl status firewalld ● firewalld.service - firewalld - dynamic firewall daemon Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled; vendor preset: enabled) Active: active (running) since Thu 2023-06-08 09:53:06 JST; 9h ago Docs: man:firewalld(1) Main PID: 866 (firewalld) Tasks: 2 (limit: 100272) Memory: 40.0M CGroup: /system.slice/firewalld.service └─866 /usr/libexec/platform-python -s /usr/sbin/firewalld --nofork --nopid 6月 08 09:52:58 mattyan1053.rocky.local systemd[1]: Starting firewalld - dynamic firewall daemon... 6月 08 09:53:06 mattyan1053.rocky.local systemd[1]: Started firewalld - dynamic firewall daemon. 6月 08 09:53:07 mattyan1053.rocky.local firewalld[866]: WARNING: AllowZoneDrifting is enabled. This is considered a> [root@mattyan1053 ~]# ^status^stop systemctl stop firewalld [root@mattyan1053 ~]# ^stop^disable systemctl disable firewalld Removed /etc/systemd/system/multi-user.target.wants/firewalld.service. Removed /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service. [root@mattyan1053 ~]# systemctl status nftables ● nftables.service - Netfilter Tables Loaded: loaded (/usr/lib/systemd/system/nftables.service; disabled; vendor preset: disabled) Active: inactive (dead) Docs: man:nft(8)
selinuxを無効化します。よく知られているやつです。今のところ使いこなせないのと、LAN内でしか公開しないので無効化。
[root@mattyan1053 ~]# getenforce Enforcing [root@mattyan1053 ~]# sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/sysconfig/selinux [root@mattyan1053 ~]# sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config [root@mattyan1053 ~]# reboot [root@mattyan1053 ~]# getenforce Disabled
kubernetesはブリッジを作成して、pod間の通信はブリッジを経由して行います。そのため、iptablesがブリッジを通過するトラフィックを処理できるようにしておく必要があるようです。 ここにかかれていました。
[root@mattyan1053 ~]# cat << EOF | tee /etc/modules-load.d/containerd.conf > overlay > br_netfilter > EOF overlay br_netfilter [root@mattyan1053 ~]# cat << EOF | tee /etc/sysctl.d/99-kubernetes-cri.conf > net.ipv4.ip_forward = 1 > net.bridge.bridge-nf-call-iptables = 1 > net.bridge.bridge-nf-call-ip6tables = 1 > EOF net.ipv4.ip_forward = 1 net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 [root@mattyan1053 ~]# modprobe overlay [root@mattyan1053 ~]# modprobe br_netfilter [root@mattyan1053 ~]# sysctl --system 略 * Applying /etc/sysctl.d/99-kubernetes-cri.conf ... net.ipv4.ip_forward = 1 net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 略
tcコマンドが必要らしい((先の手順でkubeadm init
しようとしたら「ないぞ!」って怒られた))ので、インストールしておきます。帯域制御みたいのがいるんだろうか、誰か教えてください。
[root@mattyan1053 ~]# dnf install iproute-tc
kubernetesのインストール
さて、いよいよkubernetesクラスタをつくります。
コンテナランタイムの用意
まず、コンテナランタイムを用意しておきます。コンテナランタイムとしてはDockerやcontainerdを使うことができます。Dockerをインストールするとcontainerdも一緒についてくるので、Dockerをいれます。
[root@mattyan1053 ~]# dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo [root@mattyan1053 ~]# dnf -y install docker-ce [root@mattyan1053 ~]# systemctl enable docker --now Created symlink /etc/systemd/system/multi-user.target.wants/docker.service → /usr/lib/systemd/system/docker.service. [root@mattyan1053 ~]# cp -p /etc/containerd/config.toml /etc/containerd/config.toml_bak [root@mattyan1053 ~]# containerd config default | tee /etc/containerd/config.toml [root@mattyan1053 ~]# vi /etc/containerd/config.toml [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] BinaryName = "" CriuImagePath = "" CriuPath = "" CriuWorkPath = "" IoGid = 0 IoUid = 0 NoNewKeyring = false NoPivotRoot = false Root = "" ShimCgroup = "" SystemdCgroup = true # ←ここをfalseからtrueに書き換えた [root@mattyan1053 ~]# systemctl restart containerd [root@mattyan1053 ~]# systemctl restart docker
Dockerとcontainerdが両方検出されたときはDockerが優先されるらしいです*5。今回はcontainerdを使ってみます(特に理由はないけど)。 公式ドキュメントにもあるように、cgroupを変えてあげないといけないみたいです。コントロールグループ、まだよくわかっていないんですよね。
kubernetesのインストール
リポジトリを追加してインストールします。
- kubeadm: kubernetesクラスター自体を起動するコマンド群
- kubelet: クラスタの各ノードで実行されるエージェントで、各コンテナがPodで実行されていることを保証する
- kubectl: クラスターにアクセスするためのコマンドラインツール、クラスター本体にインストールする必要はないかもしれない(あるかもしれない)
[root@mattyan1053 ~]# 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 [root@mattyan1053 ~]# less /etc/yum.repos.d/kubernetes.repo [root@mattyan1053 ~]# dnf repolist repo id repo の名前 appstream Rocky Linux 8 - AppStream baseos Rocky Linux 8 - BaseOS docker-ce-stable Docker CE Stable - x86_64 extras Rocky Linux 8 - Extras kubernetes Kubernetes [root@mattyan1053 ~]# dnf install kubelet kubeadm kubectl 略 依存関係が解決しました。 ===================================================================================================================== パッケージ アーキテクチャー バージョン リポジトリー サイズ ===================================================================================================================== インストール: kubeadm x86_64 1.27.2-0 kubernetes 11 M kubectl x86_64 1.27.2-0 kubernetes 11 M kubelet x86_64 1.27.2-0 kubernetes 20 M 依存関係のインストール: conntrack-tools x86_64 1.4.4-11.el8 baseos 203 k cri-tools x86_64 1.26.0-0 kubernetes 8.6 M kubernetes-cni x86_64 1.2.0-0 kubernetes 17 M libnetfilter_cthelper x86_64 1.0.0-15.el8 baseos 23 k libnetfilter_cttimeout x86_64 1.0.0-11.el8 baseos 23 k libnetfilter_queue x86_64 1.0.4-3.el8 baseos 30 k socat x86_64 1.7.4.1-1.el8 appstream 322 k トランザクションの概要 ===================================================================================================================== インストール 10 パッケージ 略
初回起動時の設定を少しだけします。設定用yamlを出力して、一部書き換え。
[root@mattyan1053 ~]# kubeadm config print init-defaults | tee ClusterConfiguration.yaml apiVersion: kubeadm.k8s.io/v1beta3 bootstrapTokens: - groups: - system:bootstrappers:kubeadm:default-node-token token: abcdef.0123456789abcdef ttl: 24h0m0s usages: - signing - authentication kind: InitConfiguration localAPIEndpoint: advertiseAddress: 1.2.3.4 bindPort: 6443 nodeRegistration: criSocket: unix:///var/run/containerd/containerd.sock imagePullPolicy: IfNotPresent name: node taints: null --- apiServer: timeoutForControlPlane: 4m0s apiVersion: kubeadm.k8s.io/v1beta3 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: {} dns: {} etcd: local: dataDir: /var/lib/etcd imageRepository: registry.k8s.io kind: ClusterConfiguration kubernetesVersion: 1.27.0 networking: dnsDomain: cluster.local serviceSubnet: 10.96.0.0/12 scheduler: {} [root@mattyan1053 ~]# sed -i 's/ advertiseAddress: 1.2.3.4/ advertiseAddress: 192.168.50.120/' ClusterConfiguration.yaml # ネットワークインターフェースのIPを指定する [root@mattyan1053 ~]# cat << EOF | cat >> ClusterConfiguration.yaml > --- > apiVersion: kubelet.config.k8s.io/v1beta1 > kind: KubeletConfiguration > cgroupDriver: systemd > EOF
これで準備完了です。
うおお起動じゃ
[root@mattyan1053 ~]# kubeadm init --config=ClusterConfiguration.yaml 略 Your Kubernetes control-plane has initialized successfully! To start using your cluster, you need to run the following 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 略 [root@mattyan1053 ~]# systemctl status kubelet ● kubelet.service - kubelet: The Kubernetes Node Agent Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; vendor preset: disabled) [root@mattyan1053 ~]# systemctl stop docker Warning: Stopping docker.service, but it can still be activated by: docker.socket [root@mattyan1053 ~]# systemctl disable docker Removed /etc/systemd/system/multi-user.target.wants/docker.service. [root@mattyan1053 ~]# systemctl enable containerd Created symlink /etc/systemd/system/multi-user.target.wants/containerd.service → /usr/lib/systemd/system/containerd.service.
これをするとkubernetesクラスタが構築されて、kubeletも起動しています。ついでにコンテナランタイムはcontainerdを使うので、Dockerはもう落としてしまいましょう。逆にcontainerdはenableしておきます。
更にetcd
などの必要なpodは自動でつくられるようですね。次に、kubectl
使えるようにしたいので、kubectl
の向き先を自分のクラスタに向けるために、言われた通りにやります。もうそろそろroot権限じゃなくていいし、kubectlはユーザーから打ちたいのでconfigはユーザー配下のところに作ります。
[mattyan1053@mattyan1053 ~]$ mkdir -p $HOME/.kube [mattyan1053@mattyan1053 ~]$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config [mattyan1053@mattyan1053 ~]$ sudo chown $(id -u):$(id -g) $HOME/.kube/config [mattyan1053@mattyan1053 ~]$ kubectl get nodes NAME STATUS ROLES AGE VERSION node NotReady control-plane 7m19s v1.27.2
これでクラスタができました!
さて、コンテナ同士が通信するには、Container Network Interface (CNI) が必要らしいです*6。
[mattyan1053@mattyan1053 ~]$ kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.0/manifests/calico.yaml [mattyan1053@mattyan1053 ~]$ kubectl get pods --all-namespaces NAMESPACE NAME READY STATUS RESTARTS AGE kube-system calico-kube-controllers-786b679988-qpcjn 0/1 Pending 0 40s kube-system calico-node-w6bss 0/1 Init:0/3 0 40s kube-system coredns-5d78c9869d-4tbnc 0/1 Pending 0 12m kube-system coredns-5d78c9869d-5sg8w 0/1 Pending 0 12m kube-system etcd-node 1/1 Running 0 12m kube-system kube-apiserver-node 1/1 Running 0 12m kube-system kube-controller-manager-node 1/1 Running 0 12m kube-system kube-proxy-ksxll 1/1 Running 0 12m kube-system kube-scheduler-node 1/1 Running 0 12m
準備が整うと、STATUSがReadyになります。
[mattyan1053@mattyan1053 ~]$ kubectl get nodes NAME STATUS ROLES AGE VERSION node Ready control-plane 16m v1.27.2
もし、ROLESにmasterがいたら、削除してあげないとPodがこのノードに作成できないので削除してあげましょう。
$ kubectl taint nodes --all node-role.kubernetes.io/master-
最後に、コントロールプレーンノードしかないのに、Taintでコントロールプレーンノードには通常だとPodがデプロイできないようになっているので、外します。
[mattyan1053@mattyan1053 k8s-dashboard]$ kubectl describe node node | grep NoSchedule Taints: node-role.kubernetes.io/control-plane:NoSchedule [mattyan1053@mattyan1053 k8s-dashboard]$ kubectl taint nodes node node-role.kubernetes.io/control-plane:NoSchedule- node/node untainted
試しになにかデプロイしてみる
kubernetesはWeb UIでダッシュボードが使えるみたいです。これでkubernetesクラスタの状況がGUIで見れます。デフォルトだと入っていないみたいなので、デプロイしてみましょう。
[mattyan1053@mattyan1053 ~]$ wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.3/aio/deploy/recommended.yaml [mattyan1053@mattyan1053 ~]$ vi recommended.yaml --- kind: Service apiVersion: v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard spec: + type: NodePort ports: - port: 443 targetPort: 8443 + nodePort: 30843 selector: k8s-app: kubernetes-dashboard ---
ServiceがClusterIPだと、外部からアクセスできないので、NodePortにしてあげます。
あとは実際に https://<マシンのIP>:30843
にアクセスしてあげましょう。Chromeだと警告が出ますが、そりゃそうです。暗号化できていないので。LANなら特に傍受の心配はないとおもうので、警告の画面でthisisunsafe
と入力して進みます。
ダッシュボードにログインするためのアカウント作成が必要なので、公式ドキュメントに従ってつくります。
apiVersion: v1 kind: ServiceAccount metadata: name: admin-user namespace: kubernetes-dashboard --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: admin-user roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: admin-user namespace: kubernetes-dashboard
[mattyan1053@mattyan1053 k8s-dashboard]$ vi dashboard-adminuser.yaml [mattyan1053@mattyan1053 k8s-dashboard]$ kubectl apply -f dashboard-adminuser.yaml serviceaccount/admin-user created clusterrolebinding.rbac.authorization.k8s.io/admin-user created [mattyan1053@mattyan1053 k8s-dashboard]$ kubectl -n kubernetes-dashboard create token admin-user
出てきたトークンを入力します。
これで無事表示できました!
さいごに
無事できたでしょうか?自分もまだまだkubernetesのことを理解できていないので、作った環境つかっていろいろやっていきたいなーと思っています。
廊下に転がしておくにはなかなかスペック高めなので、結構きついこともできるんじゃないかなー