K8S/Kubernetes installation procedure

K8S/Kubernetes installation procedure

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 version after install
    • Docker 18.09 -
      docker --version
  • 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 module
  • ip_vs_rr: Round-robin scheduling
  • ip_vs_wrr: Weighted round-robin
  • ip_vs_sh: Source hashing
  • br_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 -xe for errors
  • If bridge-nf-call-iptables isn't set, run sysctl --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 runtime
  • kubelet: Node agent that runs pods
  • kubeadm: Cluster bootstrapping tool
  • kubectl: Command-line interface
  • kubernetes-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:

  1. Cluster join command: A kubeadm join command with a token and hash for worker nodes
  2. kubectl configuration:
    Instructions to set up kubectl access
  3. 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-address IP
  • Firewall blocking port 6443
  • Docker not running

Solutions:

  • Verify the IP with ip a or ifconfig
  • 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!

2 Comments

  1. 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.

    ReplyDelete
  2. I pay a quick visit each day some websites and sites to read articles or reviews, except this web site offers quality based posts.

    ReplyDelete
Previous Post Next Post