With the runtime and packages ready, initialize the control plane, install the Pod network, and join the workers. kubeadm creates the certificates and configuration; the kubelet runs the cluster components. Follow the sequence init → CNI → join → 통신 확인, ending with a communication check.
Table of Contents
1. Calico exclusion settings in NetworkManager
Create the below files on the three nodes to prevent NetworkManager from managing the Calico virtual interfaces. The physical NIC, ens160, continues to be managed by NetworkManager. There is no need to unify the physical NIC names for each node.
sudo tee /etc/NetworkManager/conf.d/calico.conf > /dev/null <<'EOF'
[keyfile]
unmanaged-devices=interface-name:cali*;interface-name:tunl*;interface-name:vxlan.calico;interface-name:wireguard.cali
EOF
sudo systemctl reload NetworkManager
2. kubeadm init in cp01
mkdir -p ~/k8s-lab
cd ~/k8s-lab
cat > kubeadm-init.yaml <<'EOF'
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 192.168.80.10
bindPort: 6443
nodeRegistration:
name: cp01
criSocket: unix:///run/containerd/containerd.sock
kubeletExtraArgs:
- name: node-ip
value: 192.168.80.10
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: v1.36.5
controlPlaneEndpoint: k8s-api.lab:6443
apiServer:
certSANs:
- k8s-api.lab
- 192.168.80.10
networking:
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
dnsDomain: cluster.local
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: iptables
EOF
sudo kubeadm config validate --config kubeadm-init.yaml
sudo kubeadm config images pull --config kubeadm-init.yaml
sudo kubeadm init --config kubeadm-init.yaml
Match the patch selected in part 3 to kubernetesVersion. If you receive the image first, it is easy to isolate storage connection and bootstrap errors. Just writing down the Pod band does not install the CNI. Here we put kube-proxy in iptables mode to see the packet path consistently.
# cp01의 labadmin 계정
mkdir -p "$HOME/.kube"
sudo cp /etc/kubernetes/admin.conf "$HOME/.kube/config"
sudo chown "$(id -u):$(id -g)" "$HOME/.kube/config"
chmod 600 "$HOME/.kube/config"
kubectl get nodes
kubectl -n kube-system get pods -o wide
admin.confcontains administrator credentials. It is not disclosed on Git or blog. In the absence of CNI, NotReady in cp01 and Pending in CoreDNS may appear. Check separately for problems with the API server itself not responding.
3. static Pod and control plane
sudo ls -l /etc/kubernetes/manifests
kubectl get --raw='/readyz?verbose'
The API server validates the request and updates the state, and etcd stores that state. The controller reduces the difference between declared and actual state, and the scheduler picks nodes for Pods that have not yet been deployed. The kubelet on each node coordinates container execution. The control plane manifest in the above directory is run by the kubelet as a static Pod, so editing it incorrectly can break control functionality.
4. Install Calico 3.33.0 VXLAN
The command below runs on cp01. The Calico 3.33 documentation instructs you to install CRD first. It doesn’t mix with the old operator single-file procedure. Distribution files are received and stored with a specified version.
cd ~/k8s-lab
CALICO_VERSION=v3.33.0
curl -fL -o calico-crds.yaml \
"https://raw.githubusercontent.com/projectcalico/calico/${CALICO_VERSION}/manifests/v3_projectcalico_org.yaml"
curl -fL -o tigera-operator.yaml \
"https://raw.githubusercontent.com/projectcalico/calico/${CALICO_VERSION}/manifests/tigera-operator.yaml"
sha256sum calico-crds.yaml tigera-operator.yaml > calico-manifests.sha256
kubectl create -f calico-crds.yaml
kubectl create -f tigera-operator.yaml
kubectl wait --for=condition=Established \
crd/installations.operator.tigera.io --timeout=120s
cat > calico-installation.yaml <<'EOF'
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
name: default
spec:
calicoNetwork:
bgp: Disabled
linuxDataplane: Iptables
nodeAddressAutodetectionV4:
cidrs:
- 192.168.80.0/24
ipPools:
- name: default-ipv4-ippool
blockSize: 26
cidr: 10.244.0.0/16
encapsulation: VXLAN
natOutgoing: Enabled
nodeSelector: all()
EOF
kubectl create -f calico-installation.yaml
kubectl get tigerastatus
kubectl get pods -n calico-system -o wide
The default configuration only creates an Installation for networking. To save resources on small VMs, Whisker UI and Goldmane are not added. Add-ons are reviewed following documentation from the same version. create is for first time installation, so if AlreadyExists comes up, it will check existing resources first and will not delete them in bulk.
VXLAN wraps Pod packets in UDP packets of the node address and delivers them. Even if the underlying network does not know the Pod bandwidth, it can reach Pods of other nodes through UDP 4789 connections between nodes. In the lab, we set it to encapsulate on the same subnet as well. Header space is added, so if the lower MTU has become smaller due to VPN, etc., check the MTU separately.
5. Worker join
# cp01에서 새 조인 명령 생성
sudo kubeadm token create --print-join-command
Run the generated join command as sudo on each of the two workers. Append --cri-socket unix:///run/containerd/containerd.sock and use the actual token and CA hash from the output. Keep these values private. Do not bypass verification with --discovery-token-unsafe-skip-ca-verification. If the token expires, issue a new one on cp01.
# cp01에서 실행
kubectl get nodes -o wide
kubectl wait --for=condition=Ready node --all --timeout=300s
kubectl -n kube-system rollout status deployment/coredns --timeout=180s
kubectl get tigerastatus
Check the Ready status of the three nodes, the available status of CoreDNS, and the Available status of the tigerastatus network item. The display <none> in Walker ROLES is not a failure in itself. Roles are represented by labels, so whether the join is successful or not is judged along with the status.
6. Node status and Pod DNS
kubectl run dns-check --image=busybox:1.37.0 --restart=Never \
--command -- sh -c 'nslookup kubernetes.default.svc.cluster.local'
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/dns-check --timeout=120s
kubectl logs dns-check
kubectl delete pod dns-check
Verify that the Kubernetes Service address appears in the Pod log. If it is NXDOMAIN, look at the query name, and if it is timeout, look at the CoreDNS·Service·CNI path. If you check the API and Pod DNS, it will be easier to distinguish between app deployment issues and installation issues.
reference material
- Create kubeadm cluster
- kubeadm v1beta4 Settings API
- Calico 3.33 On-Premise Installation
- Calico Installation Settings API
official documents and distribution files confirmed: October 18, 2026. The output is an example to determine completion and may vary depending on the environment.



