Kubernetes (K8S) Installation Guide: Single-Master Cluster Setup on CentOS 7
If you're building a Kubernetes cluster for development, QA, or learning purposes, a single-master setup is the most practical starting point. This comprehensive tutorial walks you through every step of installing Kubernetes 1.13.4 on CentOS 7.6 with Docker 18.09, covering the exact procedure I've used in production environments for years. We'll configure IPVS networking, disable security features (for lab use only), and initialize a fully functional cluster that works across VirtualBox, VMware, Nutanix, and cloud platforms like AWS.
This guide maintains 100% accuracy with the original commands and configurations while adding critical context about why each step matters, how to verify success, and what to do when things go wrong. By the end, you'll have a working Kubernetes cluster and understand the underlying mechanics that make it tick.
Prerequisites
Before diving into the installation, ensure you have:
- Hardware/VM requirements:
- Master node: 2 vCPUs, 2GB RAM, 20GB disk
- Worker nodes: 1 vCPU, 1GB RAM, 20GB disk (minimum)
- All nodes must have unique hostnames that resolve via DNS or /etc/hosts
- Software versions (exact matches required):
- CentOS 7.6 (1810) -
cat /etc/centos-release - Kubernetes 1.13.4 -
kubeadm versionafter install Docker 18.09 -docker --version
- CentOS 7.6 (1810) -
- Network configuration:
- All nodes must communicate on the same subnet
- Ports 6443 (API), 2379-2380 (etcd), 10250 (kubelet) must be open
- No firewalls between nodes (we'll disable firewalld)
- Security considerations:
- This setup disables SELinux and firewalld - only for lab environments
- For production, implement proper network policies and RBAC
Step 1: Configure Kernel Modules and System Settings
Kubernetes requires specific kernel modules and sysctl settings to handle container networking and load balancing. These configurations must be identical across all nodes (master and workers).
Update System and Install Dependencies
# yum update -y
Why this matters: The update ensures you have the latest security patches and kernel modules. Skipping this step can lead to obscure networking issues later.
Install and Configure IPVS Modules
IPVS (IP Virtual Server) provides load balancing for Kubernetes services. While iptables is the default, IPVS offers better performance for production workloads.
# yum install ipvsadm -y
Create the modules configuration file:
# vi /etc/modules-load.d/ip_vs.conf
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
br_netfilter
nf_conntrack_ipv4
Module breakdown:
ip_vs: Core IPVS moduleip_vs_rr: Round-robin schedulingip_vs_wrr: Weighted round-robinip_vs_sh: Source hashingbr_netfilter: Bridge netfilter (required for CNI plugins)nf_conntrack_ipv4: Connection tracking for IPv4
Configure Netfilter Bridge Settings
Kubernetes uses bridge networking for pods. This setting ensures iptables can see bridged traffic.
# vi /usr/lib/sysctl.d/00-system.conf
net.bridge.bridge-nf-call-iptables = 1
Enable IP Forwarding
IP forwarding allows pods to communicate across nodes. Without this, your cluster will have networking issues.
# vi /etc/sysctl.conf
net.ipv4.ip_forward = 1
# sysctl -p
Disable Security Features (Lab Only)
Warning: These steps disable critical security features. Only perform them in isolated lab environments.
Disable SELinux
# sed -i --follow-symlinks 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/sysconfig/selinux
Disable Swap Partition
Kubernetes doesn't work well with swap enabled. The kubelet will fail to start if swap is active.
# swapoff -a
Edit /etc/fstab and comment out any swap partition lines:
# vi /etc/fstab
#/dev/mapper/centos-swap swap swap defaults 0 0
Disable Firewalld
# systemctl disable firewalld
Reboot and Verify Configuration
# reboot
After reboot, verify all modules are loaded:
# lsmod | grep '^\(ip_vs\|ip_vs_rr\|ip_vs_wrr\|ip_vs_sh\|nf_conntrack_ipv4\|br_netfilter\)'
nf_conntrack_ipv4 15053 0
br_netfilter 22256 0
ip_vs_sh 12688 0
ip_vs_wrr 12697 0
ip_vs_rr 12600 0
ip_vs 145497 6 ip_vs_rr,ip_vs_sh,ip_vs_wrr
Verify the netfilter bridge setting:
# sysctl -a | grep bridge-nf-call-iptables
net.bridge.bridge-nf-call-iptables = 1
Troubleshooting:
- If modules don't load, check
journalctl -xefor errors - If
bridge-nf-call-iptablesisn't set, runsysctl --system - Verify swap is disabled with
free -m(should show 0 under swap)
Step 2: Install Docker and Kubernetes Components
This step installs the container runtime (Docker) and Kubernetes components on all nodes (master and workers).
Install Required Packages
Docker and Kubernetes require device-mapper and LVM2 for storage management.
# yum install -y yum-utils device-mapper-persistent-data lvm2
Add Docker Repository
# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
Add Kubernetes Repository
The Kubernetes repository requires GPG key verification. This configuration ensures you get official packages.
# 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
Install Docker and Kubernetes
We install specific versions to match the original procedure. The --nogpgcheck flag is used here because the original post specified it, though in production you might want to verify GPG signatures.
# yum install -y --nogpgcheck docker-ce kubelet kubeadm kubectl kubernetes-cni
Package breakdown:
docker-ce: Docker container runtimekubelet: Node agent that runs podskubeadm: Cluster bootstrapping toolkubectl: Command-line interfacekubernetes-cni: Container Network Interface plugins
Start and Enable Services
The kubelet won't start until you initialize the cluster, but it's important to enable it now.
# systemctl start docker && systemctl enable docker
# systemctl start kubelet && systemctl enable kubelet
Reboot for Good Measure
# reboot
Verification steps:
- Check Docker version:
docker --version(should show 18.09.x) - Check kubelet status:
systemctl status kubelet(will show "activating" until cluster init) - Check kubeadm version:
kubeadm version(should show 1.13.4)
Step 3: Initialize the Master Node
This step transforms your master node into a Kubernetes control plane, running the API server, scheduler, controller manager, and etcd database.
Run kubeadm init
The kubeadm init command bootstraps the control plane components as static pods managed by the kubelet. The --apiserver-advertise-address must match your master node's IP address.
# kubeadm init --apiserver-advertise-address <yourMasternodeIP> --pod-network-cidr=192.168.0.0/16
Example with a sample IP:
# kubeadm init --apiserver-advertise-address 10.3.36.34 --pod-network-cidr=192.168.0.0/16
Key flags explained:
--apiserver-advertise-address: The IP address the API server will bind to--pod-network-cidr: The IP range for pod networking (must match your CNI plugin)
Understand the kubeadm init Output
The output contains three critical sections:
- Cluster join command: A
kubeadm joincommand with a token and hash for worker nodes
Instructions to set up kubectl accesskubectl configuration:- Pod network setup: Reminder to install a CNI plugin
Example output (save this!):
[init] Using Kubernetes version: v1.13.4
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Activating the kubelet service
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 10.3.36.34]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [localhost] and IPs [127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [k8s-master] and IPs [10.3.36.34 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 20.502138 seconds
[uploadconfig] storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.13" in namespace kube-system with the configuration for the kubelets in the cluster
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "k8s-master" as an annotation
[mark-control-plane] Marking the node k8s-master as control-plane by adding the label "node-role.kubernetes.io/master=''"
[mark-control-plane] Marking the node k8s-master as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[bootstrap-token] Using token: abcdef.0123456789abcdef
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] creating the "cluster-info" ConfigMap in the "kube-public" namespace
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy
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
You can now join any number of machines by running the following on each node
as root:
kubeadm join 10.3.36.34:6443 --token abcdef.0123456789abcdef --discovery-token-ca-cert-hash sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
Set Up kubectl Access
As a regular user (not root), run these commands to configure kubectl:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Verify Master Node Status
Check that the control plane pods are running:
$ kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-86c58d9df4-299ht 0/1 Pending 0 2m
coredns-86c58d9df4-wx5qk 0/1 Pending 0 2m
etcd-k8s-master 1/1 Running 0 1m
kube-apiserver-k8s-master 1/1 Running 0 1m
kube-controller-manager-k8s-master 1/1 Running 0 1m
kube-proxy-5x8c2 1/1 Running 0 2m
kube-scheduler-k8s-master 1/1 Running 0 1m
Note that CoreDNS pods are in Pending state - this is expected until we install a CNI plugin.
Install a CNI Plugin (Calico)
Kubernetes requires a Container Network Interface (CNI) plugin for pod networking. We'll use Calico, which works well with the 192.168.0.0/16 CIDR we specified.
$ kubectl apply -f https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/rbac-kdd.yaml
$ kubectl apply -f https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml
Verify all pods are running:
$ kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
calico-node-abc12 2/2 Running 0 1m
coredns-86c58d9df4-299ht 1/1 Running 0 5m
coredns-86c58d9df4-wx5qk 1/1 Running 0 5m
etcd-k8s-master 1/1 Running 0 4m
kube-apiserver-k8s-master 1/1 Running 0 4m
kube-controller-manager-k8s-master 1/1 Running 0 4m
kube-proxy-5x8c2 1/1 Running 0 5m
kube-scheduler-k8s-master 1/1 Running 0 4m
Step 4: Join Worker Nodes to the Cluster
Now that the master is running, we'll join worker nodes using the kubeadm join command from the master's initialization output.
Run the Join Command on Each Worker
On each worker node, run the join command exactly as it appeared in the master's output:
# kubeadm join 10.3.36.34:6443 --token abcdef.0123456789abcdef --discovery-token-ca-cert-hash sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
What this command does:
- Contacts the API server on the master node
- Uses the bootstrap token for authentication
- Verifies the CA certificate hash
- Downloads the cluster configuration
- Starts the kubelet and kube-proxy
Verify Node Status
On the master node, check that workers have joined:
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 10m v1.13.4
k8s-node1 Ready <none> 1m v1.13.4
k8s-node2 Ready <none> 1m v1.13.4
It may take a minute for nodes to show as Ready. If they remain in NotReady state, check the kubelet logs on the worker:
# journalctl -u kubelet -f
Common Pitfalls and Troubleshooting
Even with careful execution, you might encounter issues. Here are the most common problems and their solutions:
1. kubeadm init Fails with "connection refused"
Symptoms: kubeadm init fails with "connection refused" to the API server.
Causes:
- Incorrect
--apiserver-advertise-addressIP - Firewall blocking port 6443
Docker not running
Solutions:
- Verify the IP with
ip aorifconfig - Check firewall status:
systemctl status firewalld - Restart Docker:
systemctl restart docker
2. Nodes Stuck in NotReady State
Symptoms: Worker nodes show NotReady in kubectl get nodes.
Causes:
- CNI plugin not installed
- Network connectivity issues between nodes
- kubelet not running on worker
Solutions:
- Verify CNI plugin is installed:
kubectl get pods -n kube-system | grep calico - Check network connectivity:
ping <master-ip>,telnet <master-ip> 6443 - Check kubelet status:
systemctl status kubelet
3. CoreDNS Pods Stuck in Pending
Symptoms: CoreDNS pods show Pending status.
Causes:
- CNI plugin not installed
- Insufficient resources
- Incorrect
--pod-network-cidr
Solutions:
- Install CNI plugin (Calico as shown above)
- Check resources:
🛒 Recommended gear on Amazon
Disclosure: some links above are affiliate links — if you buy through them I may earn a small commission at no extra cost to you. Thanks for supporting the channel!
Useful info. Fortunate me I found your web site accidentally, and I am surprised why this twist of fate did not took place in advance! I bookmarked it.
ReplyDeleteI pay a quick visit each day some websites and sites to read articles or reviews, except this web site offers quality based posts.
ReplyDelete