Setup kubernetes cluster in Ubuntu 20.04 from scratch

Hello again, this article is a walk through how to setup your own kubernetes cluster with Ubuntu 20.04 LTS. Some steps are very straightforward, and you can directly follow along while you try to setup yourself.

So before get started, I tried this using 2 ubuntu servers :

    • ks8-master : 2gb memory, 2vCPUs
    • k8s-node-0 : 1gb memory, 1vCPU

I believe this is the cheapest kubernetes cluster specs that you can get. The purpose of this is only to try to init the cluster from the get-go and do the simple deployment. So here it goes :

Install the docker and disable the swap on all k8s nodes :

$ sudo apt update
$ sudo apt install -y docker.io
$ sudo systemctl start docker
$ sudo systemctl enable docker
$ sudo sed -i '/ swap / s/^\(.*\)$/#/g' /etc/fstab
$ sudo swapoff -a

Enable the port forwarding on all k8s nodes:

To enable the ip forwarding permanently, edit the file “/etc/sysctl.conf” and look for line “net.ipv4.ip_forward=1″ and un-comment it. After making the changes in the file, execute the following command :

$ sudo sysctl -p
net.ipv4.ip_forward = 1

Install k8s packages on all k8s nodes :

Execute the following command on all nodes :

$ sudo apt install -y apt-transport-https curl
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add
$ sudo apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"
$ sudo apt update
$ sudo apt install -y kubelet kubeadm kubectl

Init the cluster on k8s master :

On k8s master, now let’s init the cluster :

$ kubeadm init

This command will give you the output something like this :

Error when running kubectl

After ini the cluster, I encountered error that prevent me to run kubectl command :

The connection to the server localhost:8080 was refused – did you specify the right host or port?

If you also face the same issue, the solution is simply to run this command :

$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

Install network plugin on k8s master :

In this tutorial, I use Calico (https://www.projectcalico.org/)

$ kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml

Enable bash completion for kubectl commands

This following command is optional, but recommended. It is to enable the bash completion, when you executing kubectl sub commands. Do this on k8s master :

$ echo 'source <(kubectl completion bash)' >>~/.bashrc
$ source .bashrc

Enable nginx ingress

Enable ingress with nginx on k8s master :

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/baremetal/deploy.yaml

How to join the k8s node to k8s master :

Once the k8s master is ready, then we need to connect the k8s node to the master. We can simply do that by SSH to the k8s node, and execute the join command that we got after cluster creation completed.

$ kubeadm join 178.128.103.123:6443 --token htsn3w.juidt9j3t4zbgu3t --discovery-token-ca-cert-hash sha256:ea2e5654fb6e8bc31be463f60177f3b5d31b1da5019a20fd7a2336435b970a77

Check on the k8s master whether the nodes are ready :

$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready control-plane,master 24h v1.20.1
k8s-node-0 Ready <none> 24h v1.20.1

if you get to see the nodes ready and we’re set. Now we can continue with the deployment.

Deploy nginx on k8s cluster

Now, we come to the fun stuff.  After cluster is ready, and let’s deploy something on it. Let’s create deployment for nginx, the easy one.

From k8s master, save this file below as nginx-deployment.yml (or whatever you can call it).

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      run: nginx-deployment
  template:
    metadata:
      labels:
        run: nginx-deployment
    spec:
      containers:
      - image: nginx
        name: nginx-webserver
        ports:
        - containerPort: 8080

---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: NodePort
  selector:
    run: nginx-deployment
  ports:
    - port: 80

Then create deployment from this file :

$ kubectl create -f nginx-deployment.yml
deployment.apps/nginx-deployment created
service/nginx-service created

Check the deployment, whether it has succeed :

$ kubectl get deployments
NAME             READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 1/1   1          1         110s

Now, you see the nginx deployment has started the replica, and it’s now running fine.

Next, you can check whether the service has deployed :

$ kubectl get services
NAME          TYPE      CLUSTER-IP    EXTERNAL-IP PORT(S)      AGE
kubernetes    ClusterIP 10.96.0.1     <none>      443/TCP      34h
nginx-service NodePort  10.111.139.39 <none>      80:30992/TCP 5m27s

We can see the nginx service is already in place, and since the deployment already succeed, let’s also check whether nginx is really running by testing the cluster IP. So we can do something like :

$ curl 10.111.139.39

and the output is :

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href=”http://nginx.org/”>nginx.org</a>.<br/>
Commercial support is available at
<a href=”http://nginx.com/”>nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Yes, the nginx now running successfully!

Increase replica

Next, lets try to increase the replica of the existing deployment. We want to increase the replica from 1 to 4. By doing that, we just need to update the yml file we just deployed with.

$ vim nginx-deployment.yml

I set the font to bold to indicates that line that I altered in the file. Change the number with the desired number.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 4
  selector:
    matchLabels:
      run: nginx-deployment
  template:
    metadata:
      labels:
        run: nginx-deployment
    spec:
      containers:
      - image: nginx
        name: nginx-webserver
        ports:
        - containerPort: 8080

---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: NodePort
  selector:
    run: nginx-deployment
  ports:
    - port: 80

Save the file again, and run the command to update the deployment :

$ kubectl apply -f nginx-deployment.yml
deployment.apps/nginx-deployment unchanged
service/nginx-service unchanged

And also check whether the number of replicas have increased :

$ kubectl get deployments nginx-deployment
NAME             READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 4/4   4          4         14h

so if the number of replicas already equal with desired count, then we have successfully scaled up the service.

Another related k8s articles :

pip error after upgrade: ImportError: cannot import name ‘main’

Sometimes pip can be problematic, after upgrade the pip version, then you call it afterwards, it will fail somehow.

$ pip install --user --upgrade pip
Collecting pip
  Using cached https://files.pythonhosted.org/packages/0f/74/ecd13431bcc456ed390b44c8a6e917c1820365cbebcb6a8974d1cd045ab4/pip-10.0.1-py2.py3-none-any.whl
Installing collected packages: pip
Successfully installed pip-8.1.1
You are using pip version 8.1.1, however version 10.0.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
ImportError: cannot import name 'main'
$ pip
Traceback (most recent call last):
  File "/usr/bin/pip3", line 9, in 
    from pip import main
ImportError: cannot import name 'main'

The problem is easily can be resolved by doing this:

$ hash -d pip

Setup Systemd Service on Ubuntu 16.04

$ sudo vim /etc/systemd/system/myservice.service
[Unit]
Description=Run the service

[Service]
User=ubuntu
# change the workspace
WorkingDirectory=/usr/local/src

#path to executable. 
#executable is a bash script which calls jar file
ExecStart=/usr/local/src/somescript

SuccessExitStatus=143
TimeoutStopSec=10
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
$ sudo vim /usr/local/src/somescript
#!/bin/sh

java -jar /some/file.jar
sudo systemctl daemon-reload
sudo systemctl enable myservice.service
sudo systemctl start myservice
sudo systemctl status myservice

 

Install MySQL on Ubuntu And Skip the Password Prompt

When you install mysql on Ubuntu (I use 16.04), it requires you to fill the admin password for the first time before finishing the installation. This prompt will wait the user input until you fill the password.

This trick is very helpful if you want to install mysql in Dockerfile, and skipping the password prompt:

$ export DEBIAN_FRONTEND=noninteractive
$ sudo -E apt-get -q -y install mysql-server

Setup Simple Ruby on Rails App On Ubuntu 16.04 From Scratch

Rails is one of the most popular ruby framework out there. And now, I want to try to run the simple app on Ubuntu 16.04 machine. it’s for testing purpose.

First, update the system and install essential dependencies:

$ sudo apt-get update
$ sudo apt-get build-essential curl sudo vim

Install nodejs:

$ curl -sL https://deb.nodesource.com/setup_9.x | sudo -E bash -
$ apt-get install nodejs

Create a dedicated user for the app, for example, ubuntu user. And this also make the ubuntu user with sudo privilege and run the command without password. Which is useful to run command that needs sudo privilege in the next steps.

$ useradd ubuntu -m
$ echo 'ubuntu ALL=(root) NOPASSWD: ALL' >> /etc/sudoers

swith to ubuntu user and install GPG keys for install rvm:

$ su - ubuntu
ubuntu~$ gpg --keyserver hkp://keys.gnupg.net \ --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 \ 7D2BAF1CF37B13E2069D6956105BD0E739499BDB

Download and install rvm:

ubuntu~$ \curl -sSL https://get.rvm.io | bash -s stable

Install ruby interpreter with version 2.5.1, you might wanna change it with your preferable version:

ubuntu~$ source ~/.rvm/scripts/rvm
ubuntu~$ rvm install 2.5.1
ubuntu~$ rvm use 2.5.1 --default

Install rails with gem, and create new app without writing the Gemfile. Why? because everytime I create new app, I ended up facing errors with dependencies in Gemfile. So, it safe to setup new app without the Gemfile, we’ll create it manually later.

ubuntu~$ gem install rails
ubuntu~$ rails new app --skip-gemfile

Create the Gemfile:

ubuntu~$ touch ~/app/Gemfile
ubuntu~$ vim ~/app/Gemfile

Gemfile, fill these dependencies below into the file, save and exit:

source 'https://rubygems.org'
gem 'rails', '~> 5.2.1'
gem 'bootsnap', '~> 1.3.2'
gem 'tzinfo-data', '~> 1.2018.5'
gem 'listen', '~> 3.1.5'
gem 'sqlite3'

Now, install all the gems with bundle:

ubuntu~$ cd ~/app
ubuntu app~$ bundle install

Try run the rails:

ubuntu app~$ rails server -b 0.0.0.0

 

Setup ftp that works with local user with vsftpd on ubuntu

This is the step-by-step installation of vsftpd that actually works. If you have website that runs wordpress, you might want to enable this to be able install/update your wordpress plugin.

Install vsftpd and start the service:

$ sudo apt-get install vsftpd -y
$ sudo systemctl start vsftpd.service
$ sudo systemctl enable vsftpd.service

Open vsftpd.conf file, and make sure these lines below are enabled:

$ sudo vim /etc/vsftpd.conf
...
...
local_enable=YES
chroot_local_user=YES
allow_writeable_chroot=YES
write_enable=yes

Restart the service:

$ sudo systemctl restart vsftpd.service

Create local user:

$ sudo useradd ftpuser -s /bin/bash -md /srv/ftpuser
$ sudo passwd ftpuser

Make sure your user format is looks like this:

$ cat /etc/passwd
ftpuser:x:1000:1000::/srv/ftpuser:/bin/bash

Test your ftp user out:

$ ftp example.com

 

Setup Kubernetes on Ubuntu 16.04

Summary

This setup is supposedly to install the kubernetes on ubuntu machine with version 16.04 (64bit). I did this in the cloud and have worked perfectly.

# whoami && pwd
root
/root
# apt-get update
# apt-get install -y apt-transport-https
# curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
# echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" > /etc/apt/sources.list.d/kubernetes.list
# apt-get update -y
# apt install docker.io
# apt-get install -y kubelet kubeadm kubernetes-cni

Check the swaps, if there any, swith them off

# /proc/swaps

Init kubernetes for the first time using kubeadm:

# kubeadm init --pod-network-cidr=192.168.0.0/16 --apiserver-advertise-address=<private IP>

*Note: Change <private IP> to <public IP>, if you run the kubernetes master on single node and wish to publicly open.

# cp /etc/kubernetes/admin.conf $HOME/
# export KUBECONFIG=$HOME/admin.conf
# echo "export KUBECONFIG=$HOME/admin.conf" | tee -a ~/.bashrc

Check pods status, wait until all running

# kubectl get pods --all-namespaces

When their status are RUNNING, moving forward install the network/flannel. Please choose between these two below, I prefer to use the calico one (the second).

# kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
# kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/k8s-manifests/kube-flannel-rbac.yml

# or

# kubectl apply -f https://docs.projectcalico.org/v2.6/getting-started/kubernetes/installation/hosted/kubeadm/1.6/calico.yaml

Continue to taint the nodes:

# kubectl taint nodes --all node-role.kubernetes.io/master-

Install kubernetes dashboard

# kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml

Create user dashboard

create-user.yml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kube-system

create-role.yml

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kube-system
# kubectl create -f create-user.yml
# kubectl create -f create-role.yml
# kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')

How to set the kubernetes dashboard to publicly accessible with public IP

Read this : https://github.com/kubernetes/dashboard/wiki/Accessing-Dashboard—1.7.X-and-above

References