Install Kubernetes Cluster with Ansible on Ubuntu in 5 minutes

Install Kubernetes Cluster with Ansible Install Kubernetes Cluster with Ansible

Introduction

Install Kubernetes Cluster with Ansible, This required when we need to automate the whole installation process in a few minutes. Let’s go through step by step process. To be familairise with it, we need to know the whole step by step setup and workflow.

If you need to know the manual installation steps, have a look into this guides How to Install and configure Kubernetes (k8s) on Ubuntu 18.04 LTS. For, more Kubernetes related articles click here.

System Requirements

I’m using below system configuration in my home lab.

OPERATING SYSTEM VERSIONHOSTANMEIP ADDRESSSYSTEM CPUSYSTEM MEMORYKUBEADM VERSION
Ubuntu 16.04.6 LTS or Ubuntu 18.08 LTSk8mas1.linuxsysadmins.local192.168.0.264 vCPU8 GBv1.19.0
Ubuntu 16.04.6 LTS or Ubuntu 18.08 LTSk8nod1.linuxsysadmins.local192.168.0.274 vCPU8 GBv1.19.0
Ubuntu 16.04.6 LTS or Ubuntu 18.08 LTSk8nod1.linuxsysadmins.local192.168.0.284 vCPU8 GBv1.19.0
CentOS Linux release 8.2.2004 (Core)gateway.linuxsysadmins.local192.168.0.161 vCPU1 GBNA

Setting Up Ansible Inventory

In the first place, decide which user is going to handle this installation and which user will manage the Kubernetes cluster. In my case, I’m about to use “ansible” as my user for both.

Installing Kubernetes with Ansible
Installing Kubernetes with Ansible

Moreover, I’m using my default Ansible host inventory file for this guide and segregated the hosts in a separate group as masters and workers to ease the process to put in separate playbooks.

$ cat /etc/ansible/hosts
[users]
k8mas1.linuxsysadmins.local
k8nod1.linuxsysadmins.local
k8nod2.linuxsysadmins.local

[masters]
master ansible_host=k8mas1.linuxsysadmins.local ansible_user=ansible

[workers]
worker1 ansible_host=k8nod1.linuxsysadmins.local ansible_user=ansible
worker2 ansible_host=k8nod2.linuxsysadmins.local ansible_user=ansible

Creating User Account

To Install the Kubernetes cluster and managing the cluster let’s create an account. While running the below playbook it will prompt to type the username required to be created on the remote servers.

---
- hosts: users
  become: yes

  vars_prompt:

   - name: "new_user"
     prompt: "Account need to be create in remote server."
     private: no

  tasks:
    - name: creating the user {{ new_user }}.
      user:
        name: "{{ new_user }}"
        createhome: yes
        shell: /bin/bash
        append: yes
        state: present  

    - name: Create a dedicated sudo entry file for the user.
      file:
        path: "/etc/sudoers.d/{{ new_user }}"
        state: touch
        mode: '0600'

    - name: "Setting up Sudo without Password for user {{ new_user }}."
      lineinfile:
        dest: "/etc/sudoers.d/{{ new_user }}"
        line: '{{ new_user }}  ALL=(ALL) NOPASSWD: ALL'
        validate: 'visudo -cf %s'

    - name: Set authorized key for user copying it from current {{ new_user }}  user.
      authorized_key:
         user: "{{ new_user }}"
         state: present
         key: "{{ lookup('file', lookup('env','HOME') + '/.ssh/id_rsa.pub') }}"

    - name: Print the created user.
      shell: id "{{ new_user }}"
      register: new_user_created

    - debug:
        msg: "{{ new_user_created.stdout_lines[0] }}"
...

Save the above yaml in a cat and run as follow.

$ ansible-playbook cretae_user.yaml -k -K

The playbook will manage to create the account, adding SUDO entry with no password to root and copying the SSH authorized key from the Ansible host to remote servers.

TASK [debug] ******************************************************************************************************
ok: [k8mas1.linuxsysadmins.local] => {
    "msg": "uid=1011(ansibleuser1) gid=1011(ansibleuser1) groups=1011(ansibleuser1)"
}
ok: [k8nod1.linuxsysadmins.local] => {
    "msg": "uid=1011(ansibleuser1) gid=1011(ansibleuser1) groups=1011(ansibleuser1)"
}
ok: [k8nod2.linuxsysadmins.local] => {
    "msg": "uid=1011(ansibleuser1) gid=1011(ansibleuser1) groups=1011(ansibleuser1)"
}

PLAY RECAP ********************************************************************************************************
k8mas1.linuxsysadmins.local : ok=7    changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
k8nod1.linuxsysadmins.local : ok=7    changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
k8nod2.linuxsysadmins.local : ok=7    changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[ansible@gateway ~]$ 

Install Kubernetes & Docker Packages.

Right after creating the user start with installing and configuring required packages on all the master and worker nodes. Additionally, disabling the swap, resolving dependencies are included as well. At the end of the installation, it will reboot all the nodes.

---
- hosts: "masters, workers"
  remote_user: ansible
  become: yes
  become_method: sudo
  become_user: root
  gather_facts: yes
  connection: ssh
  
  tasks:
   
   - name: Make the Swap inactive
     command: swapoff -a

   - name: Remove Swap entry from /etc/fstab.
     lineinfile:
       dest: /etc/fstab
       regexp: swap
       state: absent

   - name: Installing Prerequisites for Kubernetes
     apt: 
       name:
         - apt-transport-https
         - ca-certificates
         - curl
         - gnupg-agent
         - vim
         - software-properties-common
       state: present

   - name: Add Docker’s official GPG key
     apt_key:
       url: https://download.docker.com/linux/ubuntu/gpg
       state: present

   - name: Add Docker Repository
     apt_repository:
       repo: deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable
       state: present
       filename: docker
       mode: 0600

   - name: Install Docker Engine.
     apt: 
       name:
         - docker-ce
         - docker-ce-cli
         - containerd.io
       state: present

   - name: Enable service docker, and enable persistently
     service: 
       name: docker
       enabled: yes

   - name: Add Google official GPG key
     apt_key:
       url: https://packages.cloud.google.com/apt/doc/apt-key.gpg
       state: present

   - name: Add Kubernetes Repository
     apt_repository:
       repo: deb http://apt.kubernetes.io/ kubernetes-xenial main 
       state: present
       filename: kubernetes
       mode: 0600

   - name: Installing Kubernetes Cluster Packages.
     apt: 
       name:
         - kubeadm
         - kubectl
         - kubelet
       state: present

   - name: Enable service kubelet, and enable persistently
     service: 
       name: kubelet
       enabled: yes

   - name: Reboot all the kubernetes nodes.
     reboot:
       post_reboot_delay: 10
       reboot_timeout: 40
       connect_timeout: 60
       test_command: uptime
...

Save the yaml as follow and run it.

$ ansible-playbook pacakge_install.yaml -k -K

Output for your reference.

TASK [Reboot all the kubernetes nodes.] ***************************************************************************
changed: [worker2]
changed: [master]
changed: [worker1]

PLAY RECAP *******************************************************************************************************
master                     : ok=13   changed=8    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
worker1                    : ok=13   changed=8    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
worker2                    : ok=13   changed=8    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[ansible@gateway ~]$ 

Next step is to setup the master node.

Setting up Kubernetes Master Server

It’s now time to start the master server initialization. Before running the playbook decide which pod network CIDR, Pod Network, Networking and Network Policy needs to be created.

  • If you need to use flannel as your network use.
https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
  • In case, your choice is Calico get it from here.
https://docs.projectcalico.org/v3.16/manifests/calico.yaml

For Weave get the same from here.

https://cloud.weave.works/k8s/net

Role based access control

https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/rbac-kdd.yaml

While prompting use the required network and RBAC URL.

---
- hosts: masters
  remote_user: ansible
  become: yes
  become_method: sudo
  become_user: root
  gather_facts: yes
  connection: ssh

  vars_prompt:

   - name: "pod_network_cidr"
     prompt: "Enter the Pod Network CIDR, example: 192.168.100.0/24"
     private: no

   - name: "k8s_master_ip"
     prompt: "Enter the Apiserver advertise address, example: 192.168.0.26"
     private: no

   - name: "pod_network_manifest_file"
     prompt: "Enter the Pod network manifest file URL, Your choice could be flannel, weave or calico, etc."
     private: no

   - name: "rbac_manifest_file"
     prompt: "Enter the RBAC manifest file URL"
     private: no 

  tasks:

   - name: Intilizing Kubernetes Cluster
     command: kubeadm init --pod-network-cidr "{{ pod_network_cidr }}"  --apiserver-advertise-address "{{ k8s_master_ip }}"
     run_once: true
     delegate_to: "{{ k8s_master_ip }}"

   - pause: seconds=30

   - name: Create directory for kube config.
     become_user: ansible
     become_method: sudo
     become: yes
     file: 
       path: /home/{{ansible_user }}/.kube
       state: directory
       owner: "{{ ansible_user }}"
       group: "{{ ansible_user }}"
       mode: 0755

   - name: Copy /etc/kubernetes/admin.conf to user's home directory /home/{{ ansible_user }}/.kube/config.
     become_user: root
     become_method: sudo
     become: yes
     copy:
       src: /etc/kubernetes/admin.conf
       dest: /home/{{ ansible_user }}/.kube/config
       remote_src: yes
       owner: "{{ ansible_user }}"
       group: "{{ ansible_user }}"
       mode: '0644'

   - pause: seconds=10

   - name: Remove the cache directory.
     become_user: ansible
     become_method: sudo
     become: yes
     file: 
       path: /home/{{ ansible_user }}/.kube/cache
       state: absent

   - name: Create Pod Network & RBAC.
     become_user: ansible
     become_method: sudo
     become: yes
     command: "{{ item }}"
     with_items: 
        - kubectl apply -f {{ pod_network_manifest_file }}
        - kubectl apply -f {{ rbac_manifest_file }}

   - pause: seconds=30

   - name: Get the token for joining the nodes with Kuberentes master.
     shell: kubeadm token create  --print-join-command
     register: kubernetes_join_command

   - debug:
       msg: "{{ kubernetes_join_command.stdout }}"

   - name: Copy join command to local file.
     local_action: copy content="{{ kubernetes_join_command.stdout_lines[0] }}" dest="/tmp/kubernetes_join_command" mode=0777
...

Truncated the long output.

[ansible@gateway ~]$ ansible-playbook cluster_init.yaml -k -K
SSH password: 
BECOME password[defaults to SSH password]: 
Enter the Pod Network CIDR, example: 192.168.100.0/24: 192.168.100.0/24
Enter the Apiserver advertise address, example: 192.168.0.26: 192.168.0.26
Enter the Pod network manifest file URL, Your choice could be flannel, weave or calico, etc.: https://docs.projectcalico.org/v3.16/manifests/calico.yaml
Enter the RBAC manifest file URL: https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/rbac-kdd.yaml

PLAY [masters] ***************************************************************************************************

TASK [debug] *****************************************************************************************************
ok: [master] => {
    "msg": "kubeadm join 192.168.0.26:6443 --token h6s1ox.kqqfpl5to1ga0bo6     --discovery-token-ca-cert-hash sha256:19c2bc5db8dca256f44e9a992c599c6455ce243148edf9b170d75b9f4b3b5712 "
}

TASK [Copy join command to local file.] **************************************************************************
changed: [master]

PLAY RECAP *******************************************************************************************************
master                     : ok=12   changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[ansible@gateway ~]$ 

As before, save in a file and run it.

$ ansible-playbook cluster_init.yaml -k -K

At the end of maser server setup it will print the worker nodes join command.

Setting Up Worker Nodes

Once the master server setup completed, start with working on joining the worker nodes with the master by running the below playbook.

---
- hosts: workers
  remote_user: ansible
  become: yes
  become_method: sudo
  become_user: root
  gather_facts: yes
  connection: ssh
       
  tasks:

   - name: Copy join command to worker nodes.
     become: yes
     become_method: sudo
     become_user: root
     copy:
       src: /tmp/kubernetes_join_command
       dest: /tmp/kubernetes_join_command
       mode: 0777   

   - name: Join the Worker nodes with the master.
     become: yes
     become_method: sudo
     become_user: root
     command: sh /tmp/kubernetes_join_command
     register: joined_or_not

   - debug:
       msg: "{{ joined_or_not.stdout }}"

- hosts: masters
  remote_user: ansible
  become: yes
  become_method: sudo
  become_user: root
  gather_facts: yes
  connection: ssh
       
  tasks:

   - name: Configure kubectl command auto-completion.
     lineinfile:
       dest: /home/{{ ansible_user }}/.bashrc
       line: 'source <(kubectl completion bash)'
       insertafter: EOF
...

Output for reference.

TASK [debug] ****************************************************************************************************
ok: [worker1] => {
    "msg": "[preflight] Running pre-flight checks\n[preflight] Reading configuration from the cluster...\n[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'\n[kubelet-start] Writing kubelet configuration to file \"/var/lib/kubelet/config.yaml\"\n[kubelet-start] Writing kubelet environment file with flags to file \"/var/lib/kubelet/kubeadm-flags.env\"\n[kubelet-start] Starting the kubelet\n[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...\n\nThis node has joined the cluster:\n* Certificate signing request was sent to apiserver and a response was received.\n* The Kubelet was informed of the new secure connection details.\n\nRun 'kubectl get nodes' on the control-plane to see this node join the cluster."
}
ok: [worker2] => {
    "msg": "[preflight] Running pre-flight checks\n[preflight] Reading configuration from the cluster...\n[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'\n[kubelet-start] Writing kubelet configuration to file \"/var/lib/kubelet/config.yaml\"\n[kubelet-start] Writing kubelet environment file with flags to file \"/var/lib/kubelet/kubeadm-flags.env\"\n[kubelet-start] Starting the kubelet\n[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...\n\nThis node has joined the cluster:\n* Certificate signing request was sent to apiserver and a response was received.\n* The Kubelet was informed of the new secure connection details.\n\nRun 'kubectl get nodes' on the control-plane to see this node join the cluster."
}

PLAY [masters] **************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************
ok: [master]

TASK [Configure kubectl command auto completion.] ***************************************************************
changed: [master]

PLAY RECAP *******************************************************************************************************
master                     : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
worker1                    : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
worker2                    : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[ansible@gateway ~]$ 

Finally, copy the worker node setup yaml and save the file in the name worker.yaml and run it.

$ ansible-playbook worker.yaml -k -K

That’s it, we are good with setting up the Kubernetes cluster.

1 Playbook to Install Kubernetes cluster with Ansible

If you are interested to put all the plays in a single playbook you can do the same similar to below.

---
- hosts: "masters, workers"
  remote_user: ansible
  become: yes
  become_method: sudo
  become_user: root
  gather_facts: yes
  connection: ssh
  
  tasks:
   
   - name: Make the Swap inactive
     command: swapoff -a

   - name: Remove Swap entry from /etc/fstab.
     lineinfile:
       dest: /etc/fstab
       regexp: swap
       state: absent

   - name: Installing Prerequisites for Kubernetes
     apt: 
       name:
         - apt-transport-https
         - ca-certificates
         - curl
         - gnupg-agent
         - vim
         - software-properties-common
       state: present

   - name: Add Docker’s official GPG key
     apt_key:
       url: https://download.docker.com/linux/ubuntu/gpg
       state: present

   - name: Add Docker Repository
     apt_repository:
       repo: deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable
       state: present
       filename: docker
       mode: 0600

   - name: Install Docker Engine.
     apt: 
       name:
         - docker-ce
         - docker-ce-cli
         - containerd.io
       state: present

   - name: Enable service docker, and enable persistently
     service: 
       name: docker
       enabled: yes

   - name: Add Google official GPG key
     apt_key:
       url: https://packages.cloud.google.com/apt/doc/apt-key.gpg
       state: present

   - name: Add Kubernetes Repository
     apt_repository:
       repo: deb http://apt.kubernetes.io/ kubernetes-xenial main 
       state: present
       filename: kubernetes
       mode: 0600

   - name: Installing Kubernetes Cluster Packages.
     apt: 
       name:
         - kubeadm
         - kubectl
         - kubelet
       state: present

   - name: Enable service kubelet, and enable persistently
     service: 
       name: kubelet
       enabled: yes

   - name: Reboot all the kubernetes nodes.
     reboot:
       post_reboot_delay: 10
       reboot_timeout: 40
       connect_timeout: 60
       test_command: uptime

   - pause: seconds=20

- hosts: masters
  remote_user: ansible
  become: yes
  become_method: sudo
  become_user: root
  gather_facts: yes
  connection: ssh

  vars_prompt:

   - name: "pod_network_cidr"
     prompt: "Enter the Pod Network CIDR, example: 192.168.100.0/24"
     private: no

   - name: "k8s_master_ip"
     prompt: "Enter the Apiserver advertise address, example: 192.168.0.26"
     private: no

   - name: "pod_network_manifest_file"
     prompt: "Enter the Pod network manifest file URL, Your choice could be flannel, weave or calico, etc."
     private: no

   - name: "rbac_manifest_file"
     prompt: "Enter the RBAC manifest file URL"
     private: no 

  tasks:

   - name: Intilizing Kubernetes Cluster
     command: kubeadm init --pod-network-cidr "{{ pod_network_cidr }}"  --apiserver-advertise-address "{{ k8s_master_ip }}"
     run_once: true
     delegate_to: "{{ k8s_master_ip }}"

   - pause: seconds=30

   - name: Create directory for kube config.
     become_user: ansible
     become_method: sudo
     become: yes
     file: 
       path: /home/{{ansible_user }}/.kube
       state: directory
       owner: "{{ ansible_user }}"
       group: "{{ ansible_user }}"
       mode: 0755

   - name: Copy /etc/kubernetes/admin.conf to user's home directory /home/{{ ansible_user }}/.kube/config.
     become_user: root
     become_method: sudo
     become: yes
     copy:
       src: /etc/kubernetes/admin.conf
       dest: /home/{{ ansible_user }}/.kube/config
       remote_src: yes
       owner: "{{ ansible_user }}"
       group: "{{ ansible_user }}"
       mode: '0644'

   - pause: seconds=10

   - name: Remove the cache directory.
     become_user: ansible
     become_method: sudo
     become: yes
     file: 
       path: /home/{{ ansible_user }}/.kube/cache
       state: absent

   - name: Create Pod Network & RBAC.
     become_user: ansible
     become_method: sudo
     become: yes
     command: "{{ item }}"
     with_items: 
        - kubectl apply -f {{ pod_network_manifest_file }}
        - kubectl apply -f {{ rbac_manifest_file }}

   - pause: seconds=30

   - name: Get the token for joining the nodes with Kuberentes master.
     shell: kubeadm token create  --print-join-command
     register: kubernetes_join_command

   - debug:
       msg: "{{ kubernetes_join_command.stdout }}"

   - name: Copy join command to local file.
     local_action: copy content="{{ kubernetes_join_command.stdout_lines[0] }}" dest="/tmp/kubernetes_join_command" mode=0777

- hosts: workers
  remote_user: ansible
  become: yes
  become_method: sudo
  become_user: root
  gather_facts: yes
  connection: ssh
       
  tasks:

   - name: Copy join command to worker nodes.
     become: yes
     become_method: sudo
     become_user: root
     copy:
       src: /tmp/kubernetes_join_command
       dest: /tmp/kubernetes_join_command
       mode: 0777   

   - name: Join the Worker nodes with the master.
     become: yes
     become_method: sudo
     become_user: root
     command: sh /tmp/kubernetes_join_command
     register: joined_or_not

   - debug:
       msg: "{{ joined_or_not.stdout }}"

- hosts: masters
  remote_user: ansible
  become: yes
  become_method: sudo
  become_user: root
  gather_facts: yes
  connection: ssh
       
  tasks:

   - name: Configure kubectl command auto-completion.
     lineinfile:
       dest: /home/{{ ansible_user }}/.bashrc
       line: 'source <(kubectl completion bash)'
       insertafter: EOF
...

Running all in one playbook too works fine. Output for your reference.

TASK [Configure kubectl command auto completion.] ****************************************************************
changed: [master]

PLAY RECAP *******************************************************************************************************
master                     : ok=28   changed=15   unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
worker1                    : ok=17   changed=10   unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
worker2                    : ok=17   changed=10   unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[ansible@gateway ~]$ 

In case, if the installation fail at any stage, run below command on all the three nodes and re-run the playbook.

$ sudo kubeadm reset --ignore-preflight-errors=all

That’s it, we have successfully completed with the installing Kubernetes cluster with ansible.

Conclusion

Install Kubernetes Cluster with Ansible, To easy the cluster setup we have created with Ansible playbook. Usually, the manual step by step installation will take more time. By using Ansible, it can be completed within 5 minutes. Subscribe to our newsletter to receive more automation related articles.