<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Docker &#8211; Blog of Kliment Andreev &#8211; A place so I won&#039;t forget things</title>
	<atom:link href="https://blog.andreev.it/category/docker/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.andreev.it</link>
	<description></description>
	<lastBuildDate>Sun, 22 Oct 2023 13:21:00 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	
	<item>
		<title>Install Kubernetes with CRI-O and Cilium on Rocky Linux 9 / VMware / Bare Metal</title>
		<link>https://blog.andreev.it/2023/10/install-kubernetes-with-cri-o-and-cilium-on-rocky-linux-9/</link>
					<comments>https://blog.andreev.it/2023/10/install-kubernetes-with-cri-o-and-cilium-on-rocky-linux-9/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Mon, 09 Oct 2023 16:18:16 +0000</pubDate>
				<category><![CDATA[Containers]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[ESX/ESXi]]></category>
		<category><![CDATA[Rocky Linux]]></category>
		<category><![CDATA[Cilium]]></category>
		<category><![CDATA[CRI-O]]></category>
		<category><![CDATA[ESXi]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[MetalLB]]></category>
		<category><![CDATA[rocky linux]]></category>
		<category><![CDATA[VMware]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=9532</guid>

					<description><![CDATA[In this post, I&#8217;ll build a Kubernetes cluster with one master and two nodes&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post, I&#8217;ll build a Kubernetes cluster with one master and two nodes using CRI-O and Cilium. I have DNS in my environment but you can use the hosts files if needed.<br />
The OS will be Rocky Linux 9 running on ESXi 8.x but this should work on any other bare metal server too. The config for the nodes is:</p>
<p>&#8211; Master: 2 CPUs, 4GB RAM<br />
&#8211; Workers: 2 CPUs, 8GB RAM.</p>
<p>First, we&#8217;ll have to disable swap and put SELinux in permissive mode.<br />
<strong>Do this on all nodes (master + workers).</strong></p>
<pre class="brush: bash; title: ; notranslate">
sudo swapoff -a
sudo sed -i &#039;/ swap / s/^\(.*\)$/#\1/g&#039; /etc/fstab
sudo setenforce 0
sudo sed -i &#039;s/^SELINUX=enforcing$/SELINUX=permissive/&#039; /etc/selinux/config
</pre>
<p>Now, we have to open some firewall ports for Kubernetes..<br />
<strong>Do this on the master node only.</strong></p>
<pre class="brush: bash; title: ; notranslate">
sudo firewall-cmd --permanent --add-port=6443/tcp
sudo firewall-cmd --permanent --add-port=2379-2380/tcp
sudo firewall-cmd --permanent --add-port=10250/tcp
sudo firewall-cmd --permanent --add-port=10259/tcp
sudo firewall-cmd --permanent --add-port=10257/tcp
sudo firewall-cmd --reload
</pre>
<p><strong>Do this on the workers only.</strong></p>
<pre class="brush: bash; title: ; notranslate">
sudo firewall-cmd --permanent --add-port=10250/tcp
sudo firewall-cmd --permanent --add-port=30000-32767/tcp   
sudo firewall-cmd --reload
</pre>
<p>In addition, we&#8217;ll have to open the firewall ports for Cilium.<br />
<strong>Do this on all nodes</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
sudo firewall-cmd --permanent --add-port=4240/tcp 
sudo firewall-cmd --permanent --add-port=8472/udp 
sudo firewall-cmd --reload
</pre>
<p>Now, we have to make some changes that are also required. Some necessary modules and IPtables change.<br />
<strong>Do this on all nodes.</strong></p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt;EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# sysctl params required by setup, params persist across reboots
cat &lt;&lt;EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

# Apply sysctl params without reboot
sudo sysctl --system
</pre>
<p>We have to install CRI-O container runtime.<br />
<strong>Do this on all nodes.</strong></p>
<pre class="brush: bash; title: ; notranslate">
VERSION=1.22
sudo curl -L -o /etc/yum.repos.d/devel:kubic:libcontainers:stable.repo https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/CentOS_8/devel:kubic:libcontainers:stable.repo
sudo curl -L -o /etc/yum.repos.d/devel:kubic:libcontainers:stable:cri-o:${VERSION}.repo https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable:cri-o:${VERSION}/CentOS_8/devel:kubic:libcontainers:stable:cri-o:${VERSION}.repo
sudo dnf -y install cri-o cri-tools
sudo systemctl enable --now crio
sudo systemctl status crio
</pre>
<p>Then, we have to install Kubernetes.<br />
<strong>Do this on all nodes.</strong></p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt;EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
&#x5B;kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\$basearch
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
exclude=kubelet kubeadm kubectl
EOF
sudo dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
sudo systemctl enable --now kubelet
</pre>
<p><strong>On the master node only</strong>, initialize the cluster. Replace <strong>master.homelab.com</strong> with your master IP if you don&#8217;t use DNS.</p>
<pre class="brush: bash; title: ; notranslate">
sudo kubeadm init --control-plane-endpoint master.homelab.local:6443
</pre>
<p>You&#8217;ll get an output that says how to join the nodes, something like kubeadm join master&#8230; + a token<br />
<strong>On the master node</strong>, add these lines.</p>
<pre class="brush: bash; title: ; notranslate">
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
</pre>
<p>Join the nodes to the cluster.<br />
<strong>Do this on workers only.</strong></p>
<pre class="brush: bash; title: ; notranslate">
sudo kubeadm join master.homelab.local:6443 --token t2yir0.7n49rn6r57msy4j4 \
        --discovery-token-ca-cert-hash sha256:563360fcf60c49be91cdcf6486a4954c579a80c54503127ee0682ab8f86ec840
</pre>
<p>Check the nodes.<br />
<strong>Do this on the master node.</strong></p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get nodes
NAME                   STATUS   ROLES           AGE    VERSION
master.homelab.local   Ready    control-plane   116s   v1.28.2
node1.homelab.local    Ready    &lt;none&gt;          89s    v1.28.2
node2.homelab.local    Ready    &lt;none&gt;          85s    v1.28.2
</pre>
<p>&#8230;then check all the pods.<br />
<strong>Do this on the master node.</strong></p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get pods --all-namespaces
NAMESPACE     NAME                                           READY   STATUS    RESTARTS   AGE
kube-system   coredns-5dd5756b68-5ljgg                       1/1     Running   0          2m
kube-system   coredns-5dd5756b68-jp8tq                       1/1     Running   0          2m
kube-system   etcd-master.homelab.local                      1/1     Running   0          2m6s
kube-system   kube-apiserver-master.homelab.local            1/1     Running   0          2m6s
kube-system   kube-controller-manager-master.homelab.local   1/1     Running   0          2m6s
kube-system   kube-proxy-2z64r                               1/1     Running   0          2m
kube-system   kube-proxy-mrwjv                               1/1     Running   0          96s
kube-system   kube-proxy-nj5q4                               1/1     Running   0          100s
kube-system   kube-scheduler-master.homelab.local            1/1     Running   0          2m6s
</pre>
<p>Install Cilium<strong> on the master node only.</strong></p>
<pre class="brush: bash; title: ; notranslate">
CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
CLI_ARCH=amd64
if &#x5B; &quot;$(uname -m)&quot; = &quot;aarch64&quot; ]; then CLI_ARCH=arm64; fi
curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
sha256sum --check cilium-linux-${CLI_ARCH}.tar.gz.sha256sum
sudo tar xzvfC cilium-linux-${CLI_ARCH}.tar.gz /usr/local/bin
rm cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
cilium install --version 1.14.2
</pre>
<p>Check the status.</p>
<pre class="brush: bash; title: ; notranslate">
cilium status --wait
</pre>
<p>You should see something like this. </p>
<pre class="brush: bash; title: ; notranslate">
    /¯¯\
 /¯¯\__/¯¯\    Cilium:             OK
 \__/¯¯\__/    Operator:           OK
 /¯¯\__/¯¯\    Envoy DaemonSet:    disabled (using embedded mode)
 \__/¯¯\__/    Hubble Relay:       disabled
    \__/       ClusterMesh:        disabled

DaemonSet              cilium             Desired: 3, Ready: 3/3, Available: 3/3
Deployment             cilium-operator    Desired: 1, Ready: 1/1, Available: 1/1
Containers:            cilium             Running: 3
                       cilium-operator    Running: 1
Cluster Pods:          2/2 managed by Cilium
Helm chart version:    1.14.2
Image versions         cilium             quay.io/cilium/cilium:v1.14.2@sha256:6263f3a3d5d63b267b538298dbeb5ae87da3efacf09a2c620446c873ba807d35: 3
                       cilium-operator    quay.io/cilium/operator-generic:v1.14.2@sha256:52f70250dea22e506959439a7c4ea31b10fe8375db62f5c27ab746e3a2af866d: 1
</pre>
<p>Then you can run the connectivity test. Some of the egress test will fail because we don&#8217;t have a public IP on the cluster.</p>
<pre class="brush: bash; title: ; notranslate">
cilium connectivity test
</pre>
<p>You should be able to run your deployments now. If you need to access the pods using URL use something like NodePort or port-forwarding. It&#8217;s very cumbersome and inefficient, but you can use a load balancer with MetalLB. Read <a href="https://blog.andreev.it/2023/10/install-metallb-on-kubernetes-cluster-running-on-vmware-vms-or-bare-metal-server/" rel="noopener" target="_blank">this</a> post to see how to do that. It&#8217;s separated from this post, because it&#8217;s a separate topic than just k8s, container runtime and CNI.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2023/10/install-kubernetes-with-cri-o-and-cilium-on-rocky-linux-9/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CentOS: Install Docker and Kubernetes</title>
		<link>https://blog.andreev.it/2020/09/centos-install-docker-and-kubernetes/</link>
					<comments>https://blog.andreev.it/2020/09/centos-install-docker-and-kubernetes/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Sat, 26 Sep 2020 18:58:12 +0000</pubDate>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[CentOS 8]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[k8s]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=7122</guid>

					<description><![CDATA[I have another post regarding Docker and Kubernetes, but it&#8217;s related to CentOS 7.&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>I have <a href="https://blog.andreev.it/?p=5127" rel="noopener noreferrer" target="_blank">another post</a> regarding Docker and Kubernetes, but it&#8217;s related to CentOS 7. In this post, I&#8217;ll explain how to install Docker and Kubernetes on CentOS 8. There is not that much difference in the installation process, but some things changed so it&#8217;s easier to write a new post than modify the original one.<br />
I&#8217;ll explain how to install Docker and Kubernetes on one master and one node. You can have as many nodes you want. All commands will be executed as root but we&#8217;ll create a user called <strong>devops </strong>that will ultimately manage both Docker and Kubernetes.</p>
<h1>Docker</h1>
<p>Log as root and execute this command on both master and the node server. The highlighted line is what you type. The rest is the output.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
Adding repo from: https://download.docker.com/linux/centos/docker-ce.repo
</pre>
<p>Then install docker. This will also install Docker CLI and containerd.io. Do this on both servers too.</p>
<pre class="brush: bash; title: ; notranslate">
dnf -y install docker-ce
</pre>
<p>Let&#8217;s make sure that it starts now and on boot.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl enable --now docker
</pre>
<p>If you check the status, you&#8217;ll see that Docker is running.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
systemctl status docker | grep active
   Active: active (running) since Sat 2020-09-26 09:28:50 EDT; 50s ago
</pre>
<p>Let&#8217;s make sure it&#8217;s running and it&#8217;s functioning. Do this on one server.</p>
<pre class="brush: bash; title: ; notranslate">
docker run hello-world
</pre>
<p>You&#8217;ll see a bunch of output, some greetings and Hello from Docker! message. You don&#8217;t need this container and image so you can delete it.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                          PORTS               NAMES
bb436fac0476        hello-world         &quot;/hello&quot;            About a minute ago   Exited (0) About a minute ago                       quirky_chatelet
</pre>
<p>Look at the end. In my case, the container name is quirky_chatelet. In your case it&#8217;s something different. Remove the container.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
docker rm quirky_chatelet
quirky_chatelet
</pre>
<p>Remove the image.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
docker rmi hello-world
Untagged: hello-world:latest
Untagged: hello-world@sha256:4cf9c47f86df71d48364001ede3a4fcd85ae80ce02ebad74156906caff5378bc
Deleted: sha256:bf756fb1ae65adf866bd8c456593cd24beb6a0a061dedf42b26a993176745f6b
Deleted: sha256:9c27e219663c25e0f28493790cc0b88bc973ba3b1686355f221c38a36978ac63
</pre>
<p>We also have to make some changes how Docker uses the storage and cgroups. By default, it&#8217;s not using systemd.</p>
<pre class="brush: bash; highlight: [1,3]; title: ; notranslate">
docker info | grep Storage
 Storage Driver: overlay2
docker info | grep Cgroup
 Cgroup Driver: cgroupfs
</pre>
<p>Overlay2 is fine but cgroupfs is not. Make these changes on both servers. Just copy and paste the whole snippet and it will create a file for you.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt;EOF &gt; /etc/docker/daemon.json
{
  &quot;exec-opts&quot;: &#x5B;&quot;native.cgroupdriver=systemd&quot;],
  &quot;log-driver&quot;: &quot;json-file&quot;,
  &quot;log-opts&quot;: {
    &quot;max-size&quot;: &quot;100m&quot;
  },
  &quot;storage-driver&quot;: &quot;overlay2&quot;,
  &quot;storage-opts&quot;: &#x5B;
    &quot;overlay2.override_kernel_check=true&quot;
  ]
}
EOF
</pre>
<p>Restart Docker daemon on both servers and check the changes.</p>
<pre class="brush: bash; highlight: [1,2]; title: ; notranslate">
systemctl restart docker
docker info | grep -i cgroup
 Cgroup Driver: systemd
</pre>
<p>Finally, we need a user that will manage Docker. We don&#8217;t want to use root for that. If you don&#8217;t have a user created, do it with <strong>adduser </strong>command. In addition, we need to add this user to be a member of the group docker that was created when we installed docker. In my case the user is <strong>devops</strong>. Do this on both servers.</p>
<pre class="brush: bash; title: ; notranslate">
usermod -aG docker devops
</pre>
<p>If you log as the devops user and execute <strong>docker ps -a</strong>, you&#8217;ll see that Docker accepted your command. Otherwise, you&#8217;ll see some access denied error.</p>
<h1>Kubernetes</h1>
<p>In order to run Kubernetes (k8s) we need to make some changes to the OS. There are several prerequisites.<br />
First, we&#8217;ll have to enable bridge networking. Do this on both servers and make sure you are logged as root.</p>
<pre class="brush: bash; title: ; notranslate">
modprobe br_netfilter
echo '1' &gt; /proc/sys/net/bridge/bridge-nf-call-iptables
echo '1' &gt; /proc/sys/net/bridge/bridge-nf-call-ip6tables
</pre>
<p>Then we have to disable SELinux security.</p>
<pre class="brush: bash; title: ; notranslate">
setenforce 0
sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
</pre>
<p>We also need to disable swap disk.</p>
<pre class="brush: bash; title: ; notranslate">
swapoff -a
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
</pre>
<p>Now it&#8217;s time to install Kubernetes. Copy &#038; paste this snippet that will add the Kubernetes repo to CentOS. Do this on both servers logged as root.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt;EOF &gt; /etc/yum.repos.d/kubernetes.repo
&#x5B;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
</pre>
<p>Install Kubernetes on both. </p>
<pre class="brush: bash; title: ; notranslate">
dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
</pre>
<p>(Optional) Install this handy tool that can help with Kubernetes commands auto-complete. For example, you type kubectl then g hit tab and it will give you a choice of commands that start with g.</p>
<pre class="brush: bash; title: ; notranslate">
dnf install -y bash-completion
</pre>
<p>Before we finish the installation, we&#8217;ll have to make some changes to the firewall. If you don&#8217;t have the firewall enabled, you can skip this section.</p>
<h2>Firewall</h2>
<p>On the master node only, open these ports.</p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --add-port=6443/tcp --permanent
firewall-cmd --add-port=2379-2380/tcp --permanent
firewall-cmd --add-port=10250-10252/tcp --permanent
firewall-cmd --reload
</pre>
<p>On the worker nodes only, open these ports.</p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --add-port=10250/tcp --permanent
firewall-cmd --add-port=30000-32767/tcp --permanent
firewall-cmd --reload
</pre>
<h2>Network</h2>
<p>Now, it&#8217;s time to install the networking component. Kubernetes has several of them, such as flannel, callico, WeaveNet etc&#8230; We&#8217;ll go with flannel.<br />
Do this on the master node only. There is no need to install the network component on the worker nodes. Kubernetes will take care of that.<br />
Initialize flannel with this subnet on the master node only. This subnet will be where pods will be running. It takes a couple of minutes.</p>
<pre class="brush: bash; title: ; notranslate">
kubeadm init --pod-network-cidr=10.244.0.0/16
</pre>
<p>At the end you&#8217;ll see an output that tells how to join the worker nodes to the master node. In my case it was something like this. Copy this code and the token somewhere safe. You&#8217;ll need it anytime you need to join a worker node. Don&#8217;t execute this yet.</p>
<pre class="brush: bash; title: ; notranslate">
kubeadm join 192.168.1.115:6443 --token dgwe20.8pw4vd8ks2savzbf \
    --discovery-token-ca-cert-hash sha256:6948547d7ff058a56aba374f44a583459f87e63aacaa53179e7adfeb5cf935a6
</pre>
<p>Let&#8217;s start Kubernetes and make sure it starts on boot. Do this on the master and the worker node logged as root.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl enable --now kubelet
</pre>
<p>On the master node, log as your standard user that you made to run Docker, in my case it was devops.<br />
Run these commands, one by one.</p>
<pre class="brush: bash; title: ; notranslate">
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
echo 'source &lt;(kubectl completion bash)' &gt;&gt;~/.bashrc
</pre>
<p>Now, we have to install flannel. Do this on the master node only logged as the regular user.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
</pre>
<p>Check the nodes.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get nodes
NAME                    STATUS   ROLES    AGE   VERSION
jupiter.andreev.local   Ready    master   12m   v1.19.2
</pre>
<p>As you can see, we have only the master node. Go to the worker node and as root run this command. The command that you saved somewhere.</p>
<pre class="brush: bash; title: ; notranslate">
kubeadm join 192.168.1.115:6443 --token dgwe20.8pw4vd8ks2savzbf \
    --discovery-token-ca-cert-hash sha256:6948547d7ff058a56aba374f44a583459f87e63aacaa53179e7adfeb5cf935a6
</pre>
<p>After a minute or two, if you run the same command (get nodes) again, you&#8217;ll see the worker node too.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get nodes
NAME                     STATUS   ROLES    AGE     VERSION
ganymede.andreev.local   Ready    &lt;none&gt;   47s     v1.19.2
jupiter.andreev.local    Ready    master   3m47s   v1.19.2
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2020/09/centos-install-docker-and-kubernetes/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>DevOps: CI/CD using git, Jenkins, Docker, Kubernetes</title>
		<link>https://blog.andreev.it/2019/11/devops-ci-cd-using-git-jenkins-docker-kubernetes/</link>
					<comments>https://blog.andreev.it/2019/11/devops-ci-cd-using-git-jenkins-docker-kubernetes/#comments</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Sun, 03 Nov 2019 21:34:00 +0000</pubDate>
				<category><![CDATA[Containers]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[chat]]></category>
		<category><![CDATA[containers]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[git]]></category>
		<category><![CDATA[Jenkins]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=5869</guid>

					<description><![CDATA[In this post I&#8217;ll explain how to create a Node.js chat program, put the&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post I&#8217;ll explain how to create a Node.js chat program, put the source on Github and then using Jenkins we&#8217;ll deploy the code to Docker Hub and from there to Docker containers that are managed by Kubernetes. There are many prerequisites for this. </p>
<ul>
&#8211; Linux workstation with Node.js and git installed (I&#8217;ll use CentOS 7)<br />
&#8211; Valid <a href="https://github.com/" rel="noopener noreferrer" target="_blank">GitHub </a>account. Create a repo called chitchat.<br />
&#8211; Valid <a href="https://hub.docker.com/" rel="noopener noreferrer" target="_blank">DockerHub </a>account. Create a repo called chitchat.<br />
&#8211; Jenkins server with a public IP, git and Docker installed (I&#8217;ll use CentOS 7)<br />
&#8211; Kubernetes cluster running Docker</ul>
<p>See my other posts on how to install <a href="https://blog.andreev.it/?p=5482" rel="noopener noreferrer" target="_blank">Jenkins </a>and <a href="https://blog.andreev.it/?p=5127" rel="noopener noreferrer" target="_blank">Kubernetes </a>cluster. </p>
<h1>The chat program</h1>
<p>For this purpose I&#8217;ve found a simple Node.js chat program that uses Express framework. The program can be found <a href="https://sabe.io/tutorials/how-to-build-real-time-chat-app-node-express-socket-io" rel="noopener noreferrer" target="_blank">here</a>, but we&#8217;ll modify it a bit. On a Linux workstation (I use CentOS, so some commands might differ), log as your regular user and create some necessary directories, a directory called chitchat and two subdirectories, public and test and two subdirectories for public, called js and css.</p>
<pre class="brush: bash; title: ; notranslate">
mkdir -p chitchat/test
mkdir -p chitchat/public/{js,css} 
cd chitchat
</pre>
<p>Create the source files for the chat program. First, let&#8217;s create the HTML. Click the (+) sign to expand. Just copy and paste everything. The first line (cat) will take care of the file creation.</p>
<pre class="brush: xml; collapse: true; light: false; title: ; toolbar: true; notranslate">
cat &lt;&lt; EOF &gt; index.html
&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;title&gt;Chit Chat App&lt;/title&gt;
        &lt;link href=&quot;css/styles.css&quot; rel=&quot;stylesheet&quot; type=&quot;text/css&quot; media=&quot;screen&quot;&gt;
        &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width,minimum-scale=1,initial-scale=1&quot;&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;ul class=&quot;messages&quot;&gt;&lt;/ul&gt;
        &lt;form&gt;
            &lt;input type=&quot;text&quot; class=&quot;input&quot; autocomplete=&quot;off&quot; autofocus /&gt;
            &lt;button&gt;Send&lt;/button&gt;
        &lt;/form&gt;
        &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.dev.js&quot;&gt;&lt;/script&gt;
        &lt;script src=&quot;js/app.js&quot;&gt;&lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;
EOF
</pre>
<p>Then, the Node.js code.</p>
<pre class="brush: jscript; collapse: true; light: false; title: ; toolbar: true; notranslate">
cat &lt;&lt; EOF &gt; index.js
var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var port = process.env.PORT || 4141;
var path = require('path');

app.get('/', function(req, res){
    res.sendFile(__dirname + '/index.html');
});

app.use(express.static(path.join(__dirname + '/public')));


io.on('connection', function(socket) {

    socket.on('user_join', function(data) {
        this.username = data;
        socket.broadcast.emit('user_join', data);
    });

    socket.on('chat_message', function(data) {
        data.username = this.username;
        socket.broadcast.emit('chat_message', data);
    });

    socket.on('disconnect', function(data) {
        socket.broadcast.emit('user_leave', this.username);
    });
});

http.listen(port, function() {
    console.log('Listening on *:' + port);
});
EOF
</pre>
<p>Then, the actual code for the app.</p>
<pre class="brush: jscript; collapse: true; light: false; title: ; toolbar: true; notranslate">
cat &lt;&lt; EOF &gt; public/js/app.js
const form = document.querySelector(&quot;form&quot;);
const input = document.querySelector(&quot;.input&quot;);
const messages = document.querySelector(&quot;.messages&quot;);
const username = prompt(&quot;Please enter a nickname: &quot;, &quot;&quot;);
const socket = io();

form.addEventListener(&quot;submit&quot;, function(event) {
        event.preventDefault();

        addMessage(username + &quot;: &quot; + input.value);

        socket.emit(&quot;chat_message&quot;, {
                        message: input.value
        });

        input.value = &quot;&quot;;
        return false;
}, false);

socket.on(&quot;chat_message&quot;, function(data) {
        addMessage(data.username + &quot;: &quot; + data.message);
});

socket.on(&quot;user_join&quot;, function(data) {
        addMessage(data + &quot; just joined the chat!&quot;);
});

socket.on(&quot;user_leave&quot;, function(data) {
        addMessage(data + &quot; has left the chat.&quot;);
});

addMessage(&quot;You have joined the chat as '&quot; + username  + &quot;'.&quot;);
socket.emit(&quot;user_join&quot;, username);

function addMessage(message) {
        const li = document.createElement(&quot;li&quot;);
        li.innerHTML = message;
        messages.appendChild(li);
        window.scrollTo(0, document.body.scrollHeight);
}
EOF
</pre>
<p>And finally the CSS stylesheet.</p>
<pre class="brush: css; collapse: true; light: false; title: ; toolbar: true; notranslate">
cat &lt;&lt; EOF &gt; public/css/styles.css
body {
    margin: 0;
    font-family: sans-serif;
}

form {
    position: fixed;
    bottom: 0;
    left: 0;
    width: 100%;
    display: flex;
    box-sizing: border-box;
    padding: 0.25rem;
}

form input {
    border: 0;
    padding: 0.5rem;
    width: 100%;
    outline: 0;
    margin-right: 0.5rem;
    border-radius: 0.25rem;
    background: #ccc;
}

form button {
    width: 6rem;
    background-color: #1b8c00;
    color: white;
    border: none;
    padding: 0.5rem;
    cursor: pointer;
    border-radius: 0.25rem;
    text-transform: uppercase;
}

form button:hover {
    background-color: #166d01;
}

.messages {
    margin: 0;
    padding: 0;
    margin-bottom: 3rem;
}

.messages li {
    padding: 0.5rem;
}

.messages li:nth-child(odd) {
    background: #eee;
}
EOF
</pre>
<p>If you don&#8217;t have Node.js installed, then install it. If you already have Node.js, skip this step. While still logged as the regular user, sudo to install Node.js and npm and check the versions. Make sure you are in chitchat directory.</p>
<pre class="brush: bash; title: ; notranslate">
curl -sL https://rpm.nodesource.com/setup_10.x | sudo bash -
sudo yum -y install nodejs
node -v &amp;&amp; npm -v
</pre>
<p>Initiate a new project. Make sure you are in chitchat directory.</p>
<pre class="brush: bash; title: ; notranslate">
npm init
</pre>
<p>Answer the questions for the initialization. Just hit Enter for every question, we&#8217;ll replace this file later. Then install Express and socket.io.</p>
<pre class="brush: bash; title: ; notranslate">
npm install -save express
npm install -save socket.io
</pre>
<p>Finally, start the program. If you have a firewall enabled, you have to poke a hole.</p>
<pre class="brush: bash; title: ; notranslate">
sudo firewall-cmd --add-port=4141/tcp --zone=public --permanent
sudo firewall-cmd --reload
node index.js
</pre>
<p>The program should listen on port 4141. Open up a browser and access the Node.js app. (<strong>http://workstation_ip:4141</strong>).<br />
I&#8217;ve opened two windows and logged as UserA and UserB to test the functionality. It looks like this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P138-01.png"><img fetchpriority="high" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P138-01.png" alt="" width="562" height="386" class="aligncenter size-full wp-image-5924" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P138-01.png 562w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-01-300x206.png 300w" sizes="(max-width: 562px) 100vw, 562px" /></a></p>
<h1>The GitHub repository</h1>
<p>Now that we have the program ready, let&#8217;s upload it on GitHub. Log with your account and create a new repository. In my case, I&#8217;ll name it chitchat.<br />
On your workstation, initiate the git repo, add the files, do the first commit and push the files to GitHub. It should look like this. Replace your username and the repo name in line 5.</p>
<pre class="brush: bash; highlight: [5]; title: ; notranslate">
cd chitchat
git init
git add .
git commit -m &quot;First commit.&quot;
git remote add origin https://github.com/&lt;your_username&gt;/&lt;your_repo&gt;
git push -u origin master
</pre>
<h1>Jenkins</h1>
<h2>Install Node.js plugin, Git and Docker</h2>
<p>If you installed Jenkins with the default settings, the Git and Docker plugins are probably installed. Node.js plugin is not installed by default so we&#8217;ll have to take care of that. Go to <strong>Manage Jenkins</strong> from the menu and then <strong>Manage Plugins</strong>. From the <strong>Available</strong> tab, find the NodeJS plugin and install it.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P138-03.png"><img decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P138-03.png" alt="" width="495" height="83" class="aligncenter size-full wp-image-5927" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P138-03.png 495w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-03-300x50.png 300w" sizes="(max-width: 495px) 100vw, 495px" /></a><br />
Again, go to <strong>Manage Jenkins</strong>, <strong>Global Tool Configuration</strong> and click on <strong>Add NodeJS</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P138-16.jpg"><img decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P138-16.jpg" alt="" width="438" height="116" class="aligncenter size-full wp-image-5965" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P138-16.jpg 438w, https://blog.andreev.it/wp-content/uploads/2019/11/P138-16-300x79.jpg 300w" sizes="(max-width: 438px) 100vw, 438px" /></a><br />
Enter a name and choose the the latest 10.x version. Make sure that the <strong>Install automatically</strong> is checked. Also, I named my Node.js installation &#8220;<strong>node</strong>&#8221; and I&#8217;ll reference it as &#8220;<strong>node</strong>&#8221; further in the pipeline script. If you name your installation differently, you&#8217;ll have to change that in line 4 in the script below.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P138-17.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P138-17.png" alt="" width="769" height="410" class="aligncenter size-full wp-image-5986" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P138-17.png 769w, https://blog.andreev.it/wp-content/uploads/2019/11/P138-17-300x160.png 300w, https://blog.andreev.it/wp-content/uploads/2019/11/P138-17-768x409.png 768w, https://blog.andreev.it/wp-content/uploads/2019/11/P138-17-585x312.png 585w" sizes="(max-width: 769px) 100vw, 769px" /></a><br />
Again, go to <strong>Manage Jenkins</strong> and then <strong>Manage Plugins</strong> in the center. Click on the <strong>Installed </strong>tab and look for Docker Pipeline plugin and Git plugin. They should be there and already installed (see the grayed checkmark on the left).<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P138-18.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P138-18.jpg" alt="" width="429" height="138" class="aligncenter size-full wp-image-5989" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P138-18.jpg 429w, https://blog.andreev.it/wp-content/uploads/2019/11/P138-18-300x97.jpg 300w" sizes="(max-width: 429px) 100vw, 429px" /></a><br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P138-19.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P138-19.jpg" alt="" width="692" height="62" class="aligncenter size-full wp-image-5990" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P138-19.jpg 692w, https://blog.andreev.it/wp-content/uploads/2019/11/P138-19-300x27.jpg 300w, https://blog.andreev.it/wp-content/uploads/2019/11/P138-19-585x52.jpg 585w" sizes="(max-width: 692px) 100vw, 692px" /></a><a href="https://blog.andreev.it/wp-content/uploads/2019/11/P138-20.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P138-20.jpg" alt="" width="679" height="61" class="aligncenter size-full wp-image-5991" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P138-20.jpg 679w, https://blog.andreev.it/wp-content/uploads/2019/11/P138-20-300x27.jpg 300w, https://blog.andreev.it/wp-content/uploads/2019/11/P138-20-585x53.jpg 585w" sizes="(max-width: 679px) 100vw, 679px" /></a><br />
If not, click on the <strong>Available </strong>tab, search for these plugins and install them similarly to what you did with Node.js plugin. Once installed, SSH to the Jenkins server and install Git first. We&#8217;ll install it from the source. This is for CentOS 7 only.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y groupinstall &quot;Development Tools&quot;
yum -y install gettext-devel openssl-devel perl-CPAN perl-devel zlib-devel wget
wget https://github.com/git/git/archive/v2.23.0.tar.gz -O /tmp/git.tgz
cd /tmp
tar xzvf git.tgz 
cd git*
make configure
./configure
make install
cd /tmp
rm -Rf git*
</pre>
<p>Run <strong>git &dash;&dash;version</strong> to check if it works properly.<br />
Then, install Docker.</p>
<pre class="brush: bash; title: ; notranslate">
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum -y install docker-ce
systemctl enable docker
systemctl start docker
usermod jenkins -g docker
systemctl restart jenkins
</pre>
<p>Now, Jenkins can execute Docker commands.</p>
<h2>Jenkins pipeline</h2>
<p>Once installed from the main menu, in the upper-left corner, click on <strong>New Item</strong>. Enter the project name and choose <strong>Pipeline</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P138-02.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P138-02.png" alt="" width="468" height="299" class="aligncenter size-full wp-image-5926" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P138-02.png 468w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-02-300x192.png 300w" sizes="(max-width: 468px) 100vw, 468px" /></a><br />
Scroll down until you find the <strong>Pipeline script</strong> and paste this code. We just want to test pulling the repo from Github. Make sure you replace your username and repo name in line 10.</p>
<pre class="brush: bash; highlight: [10]; title: ; notranslate">
pipeline {
    agent any

    tools {nodejs &quot;node&quot;}

    stages {

        stage('Cloning Git') {
            steps {
                git 'https://github.com/&lt;your_name&gt;/&lt;your_repo&gt;'
            }
        }
    }
}
</pre>
<p>It looks like this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P138-04.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P138-04.png" alt="" width="806" height="561" class="aligncenter size-full wp-image-5928" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P138-04.png 806w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-04-300x209.png 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-04-768x535.png 768w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-04-585x407.png 585w" sizes="(max-width: 806px) 100vw, 806px" /></a><br />
Click on <strong>Save </strong>and once back, click on <strong>Build Now</strong> from the menu.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P138-05.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P138-05.png" alt="" width="257" height="375" class="aligncenter size-full wp-image-5929" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P138-05.png 257w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-05-206x300.png 206w" sizes="(max-width: 257px) 100vw, 257px" /></a><br />
Once completed, you&#8217;ll see that everything is running OK.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P138-06.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P138-06.png" alt="" width="604" height="303" class="aligncenter size-full wp-image-5930" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P138-06.png 604w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-06-300x150.png 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-06-585x293.png 585w" sizes="(max-width: 604px) 100vw, 604px" /></a><br />
Click on <strong>Console Output</strong> to see the actual logs from git.<br />
Now, that we have everything verified, we want to add another step of testing the application. We&#8217;ll have to install the pre-requisites for the Node.js and create a test scenario. On the workstation where your project is, create a test scenario. We&#8217;ll use <a href="https://mochajs.org/" rel="noopener noreferrer" target="_blank">mocha</a> and <a href="https://www.chaijs.com/" rel="noopener noreferrer" target="_blank">chai</a> for this. </p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt; EOF &gt; test/test.js
var chai = require('chai');
var io = require('socket.io-client')
var app = require('../index');
var client = io('http://localhost:4141');

console.log('Server started...Waiting for client connection...');
it('Client connected...', function(done) {
  client.on('connect', function (data) {
    done();
  });
});
EOF
</pre>
<p>We also need to define the pre-requisites. Change some of the lines here to suit your needs. This is the file that we are replacing, the one that <strong>npm init</strong> creates.</p>
<pre class="brush: xml; title: ; notranslate">
cat &lt;&lt; EOF &gt; package.json
{
  &quot;name&quot;: &quot;chitchat&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;A simple chat program&quot;,
  &quot;main&quot;: &quot;index.js&quot;,
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;node index.js&quot;,
    &quot;test&quot;: &quot;mocha -R spec test/test.js --exit&quot;
  },
  &quot;author&quot;: &quot;Kliment Andreev&quot;,
  &quot;license&quot;: &quot;ISC&quot;,
  &quot;dependencies&quot;: {
    &quot;express&quot;: &quot;^4.17.1&quot;,
    &quot;socket.io&quot;: &quot;^2.3.0&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;chai&quot;: &quot;^4.2.0&quot;,
    &quot;mocha&quot;: &quot;^5.2.0&quot;
  }
}
EOF
</pre>
<p>As you can see the first file goes under chitchat/test directory and the package.json file is in the root of chitchat directory. Don&#8217;t forget to push these files to GitHub.</p>
<pre class="brush: bash; title: ; notranslate">
git add .
git commit -m &quot;Added package.json and test unit&quot;
git push -u origin master
</pre>
<p>Now, go back to your project in Jenkins (click <strong>Configure </strong>on the left) and replace the pipeline script with this one. Make sure you change your name and repo in line 10.</p>
<pre class="brush: bash; collapse: true; highlight: [10]; light: false; title: ; toolbar: true; notranslate">
pipeline {
    agent any

    tools {nodejs &quot;node&quot;}

    stages {

        stage('Cloning Git') {
            steps {
                git 'https://github.com/&lt;your_name&gt;/&lt;your_repo&gt;'
            }
        }

        stage('Install dependencies') {
            steps {
                sh 'npm install'
            }
        }

        stage('Test') {
            steps {
                sh 'npm test'
            }
        }
    }
}
</pre>
<p>Click on <strong>Build Now</strong> and you&#8217;ll see that all stages completed fine. But, we want to go a step further and automate the build process anytime we make a change in the source code. In order to do that we need a webhook between GitHub and our Jenkins server. Make sure your Jenkins server is publicly available, otherwise this integration won&#8217;t work.<br />
But before we do that, let&#8217;s add the script above as a file under our source code for the chat program. Make sure you change your name and repo in line 11. This file goes in the root of the chitchat directory.</p>
<pre class="brush: bash; collapse: true; highlight: [11]; light: false; title: ; toolbar: true; notranslate">
cat &lt;&lt; EOF &gt; Jenkinsfile
pipeline {
    agent any

    tools {nodejs &quot;node&quot;}

    stages {

        stage('Cloning Git') {
            steps {
                git 'https://github.com/&lt;your_name&gt;/&lt;your_repo&gt;'
            }
        }

        stage('Install dependencies') {
            steps {
                sh 'npm install'
            }
        }

        stage('Test') {
            steps {
                sh 'npm test'
            }
        }
    }
}
EOF
</pre>
<p>Add this file to Github.</p>
<pre class="brush: bash; title: ; notranslate">
git add .
git commit -m &quot;Added Jenkinsfile&quot;
git push -u origin master
</pre>
<h1>GitHub integration with Jenkins</h1>
<p>Log in to Github and from the upper right corner, click on your avatar and click on <strong>Settings</strong>. Then on the menu on the left side, all the way to the bottom click on <strong>Developer settings</strong>. Click on <strong>Personal access tokens</strong> and click on <strong>Generate new token</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P138-07.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P138-07.png" alt="" width="792" height="208" class="aligncenter size-full wp-image-5940" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P138-07.png 792w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-07-300x79.png 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-07-768x202.png 768w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-07-585x154.png 585w" sizes="(max-width: 792px) 100vw, 792px" /></a><br />
Give a name to your token (I&#8217;ll name mine Jenkins Token) and click on <strong>admin:repo_hook</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P138-08.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P138-08.png" alt="" width="458" height="100" class="aligncenter size-full wp-image-5942" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P138-08.png 458w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-08-300x66.png 300w" sizes="(max-width: 458px) 100vw, 458px" /></a><br />
Click on <strong>Generate token</strong> after. You&#8217;ll get a hex token, click on the clipboard icon next to it. You&#8217;ll need this token for the Jenkins configuration.<br />
Go to Jenkins and click on <strong>Manage Jenkins</strong> on the left followed by <strong>Configure System</strong> in the middle. Scroll down to the Git section, click on <strong>Add GitHub</strong> server, type <strong>GitHub </strong>for <strong>Name </strong>and next to <strong>Credentials </strong>click on <strong>Add</strong>, then <strong>Jenkins</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P138-09.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P138-09.png" alt="" width="1515" height="382" class="aligncenter size-full wp-image-5943" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P138-09.png 1515w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-09-300x76.png 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-09-768x194.png 768w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-09-1024x258.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-09-1170x295.png 1170w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-09-585x148.png 585w" sizes="(max-width: 1515px) 100vw, 1515px" /></a><br />
When this window shows up, change the <strong>Kind </strong>to <strong>Secret text</strong>. Paste the token under <strong>Secret </strong>and enter something for <strong>ID </strong>and <strong>Description</strong>. Click <strong>Add</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P138-10.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P138-10.png" alt="" width="627" height="505" class="aligncenter size-full wp-image-5944" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P138-10.png 627w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-10-300x242.png 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-10-585x471.png 585w" sizes="(max-width: 627px) 100vw, 627px" /></a><br />
Once back, select the credential that we just created and click on <strong>Test connection</strong> on the right. You should get a message that the connection is fine. Make sure that <strong>Manage hooks</strong> is checked. Click on <strong>Save </strong>at the bottom once everything is OK.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P138-11.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P138-11.jpg" alt="" width="1113" height="230" class="aligncenter size-full wp-image-5948" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P138-11.jpg 1113w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-11-300x62.jpg 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-11-768x159.jpg 768w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-11-1024x212.jpg 1024w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-11-585x121.jpg 585w" sizes="(max-width: 1113px) 100vw, 1113px" /></a><br />
Go back to your project in Jenkins and click <strong>Configure </strong>on the left. Scroll down where the <strong>Pipeline </strong>is and change the <strong>Definition </strong>from <strong>Pipeline </strong>script to <strong>Pipeline script from SCM</strong>. SCM means Source Code Management. Change <strong>SCM </strong>to <strong>Git</strong>, enter the repo URL under <strong>Repositories </strong>. If your repo is not public, you&#8217;ll have to specify some credentials under <strong>Credentials</strong>. If your repo is public, you can leave them as <strong>none</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P138-12.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P138-12.png" alt="" width="1395" height="413" class="aligncenter size-full wp-image-5950" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P138-12.png 1395w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-12-300x89.png 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-12-768x227.png 768w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-12-1024x303.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-12-1170x346.png 1170w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-12-585x173.png 585w" sizes="(max-width: 1395px) 100vw, 1395px" /></a><br />
Then scroll up a bit and check <strong>GitHub hook trigger for GitScm polling</strong> and then click <strong>Save</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P138-13.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P138-13.png" alt="" width="420" height="306" class="aligncenter size-full wp-image-5952" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P138-13.png 420w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-13-300x219.png 300w" sizes="(max-width: 420px) 100vw, 420px" /></a><br />
If you go back to GitHub, click on your project and all the way on the right click on <strong>Settings</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P138-14.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P138-14.png" alt="" width="812" height="114" class="aligncenter size-full wp-image-5953" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P138-14.png 812w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-14-300x42.png 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-14-768x108.png 768w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-14-585x82.png 585w" sizes="(max-width: 812px) 100vw, 812px" /></a><br />
Then click on <strong>Webhooks </strong>and you&#8217;ll see a green checkmark next to your Jenkins URL.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P138-15.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P138-15.png" alt="" width="1007" height="283" class="aligncenter size-full wp-image-5954" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P138-15.png 1007w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-15-300x84.png 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-15-768x216.png 768w, https://blog.andreev.it/wp-content/uploads/2019/10/P138-15-585x164.png 585w" sizes="(max-width: 1007px) 100vw, 1007px" /></a><br />
Go to your workstation and edit the index.html file. Change the line 4 for example so instead of Chit Chat App, replace the blank with a hyphen. e.g. Chit Chat to Chit-Chat. Save it and push to GitHib.</p>
<pre class="brush: bash; title: ; notranslate">
git add .
git commit -m &quot;Minor change in index.html&quot;
git push -u origin master
</pre>
<p>If you go back to Jenkins, you&#8217;ll see that there is a build there triggered automatically. This part is <strong>CI (Continous Integration)</strong>. But, let&#8217;s go further and expand our CI with creating a Docker image.</p>
<h1>Docker image and Docker Hub</h1>
<p>In order to create a Docker image, we need a <strong>Dockerfile</strong>. I won&#8217;t explain the meaning of each line in the Dockerfile, so just copy &#038; paste the following snippet in the chitchat directory.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt; EOF &gt; Dockerfile
FROM node:10
WORKDIR /usr/src/app
COPY package*.json ./

RUN npm install
COPY . .

EXPOSE 4141
CMD &#x5B; &quot;npm&quot;, &quot;start&quot; ]
EOF
</pre>
<p>This file will tell Docker exactly what to do when creating the image. But, we also have to tell Jenkins that we need to build the image, test the image and deploy it to Docker Hub. So, your new Jenkinsfile will look like this. Change the URLs in lines 6 and 7 to suit your needs. Before you deploy the script, go to Jenkins, click on <strong>Credentials </strong>on the left, click on <strong>System </strong>below, click on <strong>Global credentials (unrestricted)</strong> in the middle and then <strong>Add Credentials</strong> on the left. Fill out the form with your Docker Hub credentials. Whatever you put as ID for these username/password pair is what you have to put in line 8 in the script.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P138-21.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P138-21.jpg" alt="" width="524" height="369" class="aligncenter size-full wp-image-5999" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P138-21.jpg 524w, https://blog.andreev.it/wp-content/uploads/2019/11/P138-21-300x211.jpg 300w" sizes="(max-width: 524px) 100vw, 524px" /></a><br />
And, now copy &#038; paste the script in the chitchat directory. If everything goes well, you&#8217;ll see your image deployed in Docker Hub and tagged as whatever your latest build was and the tag latest too.</p>
<pre class="brush: bash; highlight: [6,7]; title: ; notranslate">
cat &lt;&lt; 'EOF' &gt; Jenkinsfile
pipeline {

    environment {
        dockerregistry = 'https://registry.hub.docker.com'
        dockerhuburl = &quot;klimenta/chitchat&quot;
        githuburl = &quot;klimenta/chitchat&quot;
        dockerhubcrd = 'dockerhub'
    }

    agent any

    tools {nodejs &quot;node&quot;}

    stages {

        stage('Clone git repo') {
            steps {
                git 'https://github.com/' + githuburl
            }
        }

        stage('Install Node.js dependencies') {
            steps {
                sh 'npm install'
            }
        }

        stage('Test App') {
            steps {
                sh 'npm test'
            }
        }

        stage('Build image') {
          steps{
            script {
              dockerImage = docker.build(dockerhuburl + &quot;:$BUILD_NUMBER&quot;)
            }
          }
        }

        stage('Test image') {
            steps {
                sh 'docker run -i ' + dockerhuburl + ':$BUILD_NUMBER npm test'
            }
        }

        stage('Deploy image') {
          steps{
            script {
              docker.withRegistry(dockerregistry, dockerhubcrd ) {
                dockerImage.push(&quot;${env.BUILD_NUMBER}&quot;)
                dockerImage.push(&quot;latest&quot;)
              }
            }
          }
        }

        stage('Remove image') {
          steps{
            sh &quot;docker rmi $dockerhuburl:$BUILD_NUMBER&quot;
          }
        }
    }
}
EOF
</pre>
<p>Of course, you&#8217;ll have to tell GitHub that there is a change is a file.</p>
<pre class="brush: bash; title: ; notranslate">
git add .
git commit -m &quot;Added Jenkinsfile for Docker&quot;
git push -u origin master
</pre>
<h1>Deployment to Kubernetes using Jenkins</h1>
<p>Now that we are finished with the CI (Continuous Integration), it&#8217;s time for the CD (Continuous Deployment). We&#8217;ll use the existing Docker image that we put on Docker Hub. First thing first, we&#8217;ll have to install a plugin for a Kubernetes integration. Go to <strong>Manage Jenkins</strong> on the left, then click on <strong>Manage plugins</strong> in the center and click on <strong>Available </strong>tab. Install the <strong>Kubernetes Continuous Deploy</strong> plugin.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P138-22.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P138-22.jpg" alt="" width="510" height="71" class="aligncenter size-full wp-image-6003" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P138-22.jpg 510w, https://blog.andreev.it/wp-content/uploads/2019/11/P138-22-300x42.jpg 300w" sizes="(max-width: 510px) 100vw, 510px" /></a><br />
Once installed and Jenkins is restarted, click on <strong>Credentials </strong>on the left, then <strong>System </strong>just below and then click on <strong>Global credentials (unrestricted)</strong> in the middle. Click on <strong>Add Credentials</strong> on the left. When this window pops up, change the <strong>Kind </strong>to <strong>Kubernetes configuration (kubeconfig)</strong>, enter an <strong>ID</strong>, in my case it&#8217;s k8s. Remember the <strong>ID</strong>, you&#8217;ll reference it in the <strong>Jenkinsfile </strong>later, type a description and select <strong>Enter directly</strong>. Before you click OK, we&#8217;ll have to go to our Kubernetes cluster and log to it.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P138-23.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P138-23.jpg" alt="" width="627" height="509" class="aligncenter size-full wp-image-6004" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P138-23.jpg 627w, https://blog.andreev.it/wp-content/uploads/2019/11/P138-23-300x244.jpg 300w, https://blog.andreev.it/wp-content/uploads/2019/11/P138-23-585x475.jpg 585w" sizes="(max-width: 627px) 100vw, 627px" /></a><br />
Once you are logged to your master node, log as the user that you use to manage the k8s cluster and run this command.</p>
<pre class="brush: bash; title: ; notranslate">
cat .kube/config
</pre>
<p>You&#8217;ll get a long list of text. Paste this back in the Jenkins window under <strong>Content</strong> and click OK. This is how Jenkins will communicate with your Kubernetes cluster. We also need to tell Kubernetes how we want our app deployed. As you know this is done with a YAML file. So, we have to add that YAML file under the chitchat directory.Change the IP in line 25 to match your master k8s node IP. I am deploying a load balancer that will listen on port 8080 and 2 pods in a deployment.</p>
<pre class="brush: xml; title: ; notranslate">
cat &lt;&lt; 'EOF' &gt; k8s.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: chitchat
spec:
  replicas: 2
  template:
    metadata:
      name: chitchat
      labels:
        app: chitchat
    spec:
      containers:
      - image: $dockerhuburl:$BUILD_NUMBER
        name: nodejs
---
apiVersion: v1
kind: Service
metadata:
  name: loadbalancer
spec:
  type: LoadBalancer
  externalIPs:
  - 192.168.1.7
  selector:
    app: chitchat
  ports:
  - port: 8080
    targetPort: 4141
EOF
</pre>
<p>And we also want to tell Jenkins that we are adding an extra step to deploy it to the cluster, so the final Jenkins file looks like this.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt; 'EOF' &gt; Jenkinsfile
pipeline {

  environment {
    dockerregistry = 'https://registry.hub.docker.com'
    dockerhuburl = 'klimenta/chitchat'
    githuburl = 'klimenta/chitchat'
    dockerhubcrd = 'dockerhub'
    dockerImage = ''
  }

  agent any

  tools {nodejs &quot;node&quot;}

  stages {

    stage('Clone git repo') {
      steps {
         git 'https://github.com/' + githuburl
      }
    }

    stage('Install Node.js dependencies') {
      steps {
        sh 'npm install'
      }
    }

    stage('Test App') {
        steps {
            sh 'npm test'
        }
    }

    stage('Build image') {
      steps{
        script {
          dockerImage = docker.build(dockerhuburl + &quot;:$BUILD_NUMBER&quot;)
        }
      }
    }

    stage('Test image') {
      steps {
        sh 'docker run -i ' + dockerhuburl + ':$BUILD_NUMBER npm test'
      }
    }

    stage('Deploy image') {
      steps{
        script {
          docker.withRegistry(dockerregistry, dockerhubcrd ) {
            dockerImage.push(&quot;${env.BUILD_NUMBER}&quot;)
            dockerImage.push(&quot;latest&quot;)
          }
        }
      }
    }

    stage('Remove image') {
      steps{
        sh &quot;docker rmi $dockerhuburl:$BUILD_NUMBER&quot;
      }
    }

    stage('Deploy k8s') {
      steps {
        kubernetesDeploy(
          kubeconfigId: 'k8s',
          configs: 'k8s.yaml',
          enableConfigSubstitution: true
        )
      }
    }
  }
}
EOF
</pre>
<p>Trigger the change.</p>
<pre class="brush: bash; title: ; notranslate">
git add .
git commit -m &quot;Added Jenkinsfile for kubernetes and yaml&quot;
git push -u origin master
</pre>
<p>After a minute or so, you&#8217;ll see your pods running and if you go to your master node on port 8080 you&#8217;ll see the app happily running.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P138-24.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P138-24.jpg" alt="" width="424" height="344" class="aligncenter size-full wp-image-6025" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P138-24.jpg 424w, https://blog.andreev.it/wp-content/uploads/2019/11/P138-24-300x243.jpg 300w" sizes="(max-width: 424px) 100vw, 424px" /></a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2019/11/devops-ci-cd-using-git-jenkins-docker-kubernetes/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title>CentOS: Kubernetes, Flannel and Calico in a single master configuration</title>
		<link>https://blog.andreev.it/2019/04/centos-kubernetes-flannel-and-calico-in-a-single-master-configuration/</link>
					<comments>https://blog.andreev.it/2019/04/centos-kubernetes-flannel-and-calico-in-a-single-master-configuration/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Tue, 09 Apr 2019 15:46:01 +0000</pubDate>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Calico]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[Flannel]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=5127</guid>

					<description><![CDATA[Kubernetes (k8s) is getting a lot of attention and it&#8217;s becoming more and more&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>Kubernetes (k8s) is getting a lot of attention and it&#8217;s becoming more and more popular even in enterprises that are quite IT conservative. In this post I&#8217;ll explain how to install Kubernetes on a single master and one node on CentOS. Then, we&#8217;ll install the networking plugins (CNI), Flannel or Calico. At the end I&#8217;ll show an example of how to deploy a simple Node.js app and do rollout update and undoing the rollout. </p>
<div style="border:1px solid red; padding:16px;">
<p style="text-align:center;"><strong><span style="color:#800000;">NOTE ABOUT VERSIONS</span> </strong></p>
<p><center>Kubernetes and the surrounding components are changed on a daily basis. What works today, might not work tomorrow.</center></p>
<p><center>This tutorial assumes that you use CentOS 7, Docker 18.x and Kubernetes 1.14.</center></p>
</div>
<p>For this post, there are some pre-requisites. You will need 2 servers with 2 CPUs and at least 2GB RAM. It is also recommended to have a working DNS. If you don&#8217;t have DNS in your lab, make sure you use <strong>/etc/hosts</strong> for hostname resolution, but you can get away if you use IPs only (not recommended). </p>
<h1>Pre-requisites</h1>
<p>On a fresh installed CentOS 7, do these pre-req commands on both the master and the node at the same time. You need to be logged as root.<br />
Make sure SELinux is disabled.</p>
<pre class="brush: bash; title: ; notranslate">
setenforce 0
sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
</pre>
<p>Kubernetes doesn&#8217;t like swap, so if you have it in <strong>/etc/fstab</strong>, disable the swap.</p>
<pre class="brush: bash; title: ; notranslate">
swapoff -a
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
</pre>
<p>Enable the bridge network module.</p>
<pre class="brush: bash; title: ; notranslate">
modprobe br_netfilter
echo '1' &gt; /proc/sys/net/bridge/bridge-nf-call-iptables
echo '1' &gt; /proc/sys/net/bridge/bridge-nf-call-ip6tables
</pre>
<p>Install Docker and change the cgroup from cfsgroup to systemd.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum -y install docker-ce
mkdir /etc/docker
cat &lt;&lt;EOF &gt; /etc/docker/daemon.json
{
  &quot;exec-opts&quot;: &#x5B;&quot;native.cgroupdriver=systemd&quot;],
  &quot;log-driver&quot;: &quot;json-file&quot;,
  &quot;log-opts&quot;: {
    &quot;max-size&quot;: &quot;100m&quot;
  },
  &quot;storage-driver&quot;: &quot;overlay2&quot;,
  &quot;storage-opts&quot;: &#x5B;
    &quot;overlay2.override_kernel_check=true&quot;
  ]
}
EOF
mkdir -p /etc/systemd/system/docker.service.d
systemctl daemon-reload
systemctl enable docker &amp;&amp; systemctl start docker
</pre>
<p>Try this line and make sure the output says <strong>systemd</strong>.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
docker info | grep -i cgroup
Cgroup Driver: systemd
</pre>
<p>Add the Kubernetes repo. </p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt;EOF &gt; /etc/yum.repos.d/kubernetes.repo
&#x5B;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
</pre>
<h1>Master Node</h1>
<p>Open the firewall <a href="https://kubernetes.io/docs/setup/independent/install-kubeadm/#check-required-ports" rel="noopener noreferrer" target="_blank">ports</a>. </p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --add-port=6443/tcp --permanent
firewall-cmd --add-port=2379-2380/tcp --permanent
firewall-cmd --add-port=10250-10252/tcp --permanent
firewall-cmd --reload
</pre>
<p>From the repo install kubelet, kubectl and kubeadm and make sure Kubernetes starts on boot.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install kubelet kubectl kubeadm
systemctl enable kubelet
</pre>
<p>Don&#8217;t start Kubernetes yet. It will fail with a message that it can&#8217;t find a config yaml file. Just initialize the cluster. This will also start the kubelet service. Pick one choice (Flannel or Calico).<br />
<strong>NOTE: This line initializes the cluster to be used for Flannel.</strong> </p>
<pre class="brush: bash; title: ; notranslate">
kubeadm init --pod-network-cidr=10.244.0.0/16
</pre>
<p><strong>NOTE: This line initializes the cluster to be used for Calico.</strong> </p>
<pre class="brush: bash; title: ; notranslate">
kubeadm init --pod-network-cidr=192.168.0.0/16
</pre>
<p>Look at the bottom of the output. You should see something like this. Lines 5,6,7 and 15 and 16 are important. </p>
<pre class="brush: plain; highlight: [5,6,7,15,16]; title: ; notranslate">
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 should now deploy a pod network to the cluster.
Run &quot;kubectl apply -f &#x5B;podnetwork].yaml&quot; with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.1.175:6443 --token 0z3iov.28rz29hxw9ft4jmg \
    --discovery-token-ca-cert-hash sha256:782d37b5c870ebebd2f17cc3ba1424f305e6a3e293afc04fc2030edfab6bf4b0
</pre>
<p>This means that the cluster initialized OK.<br />
Check the status of both Docker and Kubernetes.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl status docker | grep Active
systemctl status kubelet | grep Active
</pre>
<p>Make sure they are both running. Check <strong>/var/log/messages</strong> if you have any issues.</p>
<h1>Nodes (workers)</h1>
<p>On the worker nodes, make sure you do the same as you did on the master (swap, SELinux, Docker) except that you don&#8217;t have to install kubectl. </p>
<pre class="brush: bash; title: ; notranslate">
yum -y install kubelet kubeadm
systemctl enable kubelet
</pre>
<p>Open the firewall <a href="https://kubernetes.io/docs/setup/independent/install-kubeadm/#check-required-ports" rel="noopener noreferrer" target="_blank">ports</a>. </p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --add-port=10250/tcp --permanent
firewall-cmd --add-port=30000-32767/tcp --permanent
firewall-cmd --reload
</pre>
<p>Now, you can join the cluster. Use the command that was the output from the <strong>kubeadm init</strong> on the master (see above &#8211; lines 15 and 16).</p>
<pre class="brush: bash; title: ; notranslate">
kubeadm join 192.168.1.175:6443 --token 0z3iov.28rz29hxw9ft4jmg \
    --discovery-token-ca-cert-hash sha256:782d37b5c870ebebd2f17cc3ba1424f305e6a3e293afc04fc2030edfab6bf4b0
</pre>
<p>That&#8217;s how you join nodes to the master. Replace <strong>192.168.1.175</strong> with the IP or hostname of your master node. If everything is OK, you&#8217;ll see something like this.</p>
<pre class="brush: bash; title: ; notranslate">
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
</pre>
<h1>Kubernetes user</h1>
<p>While still logged as root on the master, create the Kubernetes user that you will use for managing the k8s cluster. In my case, I&#8217;ll create a user called k8s with <strong>secret </strong>as password.</p>
<pre class="brush: bash; title: ; notranslate">
useradd k8s -g docker
usermod -aG wheel k8s
echo -e &quot;secret\nsecret&quot; | passwd k8s
</pre>
<p>Log as this user (k8s) and execute these commands. These lines were also an output of the <strong>kubeadm init</strong> command above (5,6 and 7). </p>
<pre class="brush: bash; title: ; notranslate">
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
</pre>
<p>If you want to add another user to manage the Kubernetes cluster, make sure you execute these 3 lines above for that user. Check if everything looks good.</p>
<pre class="brush: bash; title: ; notranslate">
docker ps
</pre>
<p>You should see a bunch of Kubernetes system containers running (etcd, scheduler, API server).<br />
Then check the nodes.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get nodes
NAME                      STATUS     ROLES    AGE   VERSION
k8smaster.andreev.local   NotReady   master   12m   v1.14.0
k8snode1.andreev.local    NotReady   &lt;none&gt;   10m   v1.14.0
</pre>
<p>The reason the master and the node are not ready is because we don&#8217;t have a network for the cluster. </p>
<h1>Network CNI</h1>
<p>Depending on how you&#8217;ve initialized the cluster, pick one of the network plugins (Flannel or Calico). </p>
<h2>Flannel</h2>
<p>For the network to work, we&#8217;ll have to use one of the CNI plugins. There are many, Flannel, Weave Net, Calico etc.<br />
Let&#8217;s install Flannel. Do this on the master only logged as k8s user. The master will take care of the nodes.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
</pre>
<p>Start this little infinite loop and you&#8217;ll see that after 20-30 seconds, both the master and the node will change their status to <strong>Ready</strong>. Hit Ctrl-C to end.</p>
<pre class="brush: bash; title: ; notranslate">
while true
do
kubectl get nodes
sleep 3
done
</pre>
<p>Now, you have a fully working cluster ready. </p>
<h2>Calico</h2>
<p>For the network to work, we&#8217;ll have to use one of the CNI plugins. There are many, Flannel, Weave Net, Calico etc.<br />
Let&#8217;s install Flannel. Do this on the master only logged as k8s user. The master will take care of the nodes.</p>
<pre class="brush: bash; title: ; notranslate">
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
</pre>
<p>Start this little infinite loop and you&#8217;ll see that after 20-30 seconds, both the master and the node will change their status to <strong>Ready</strong>. Hit Ctrl-C to end.</p>
<pre class="brush: bash; title: ; notranslate">
while true
do
kubectl get nodes
sleep 3
done
</pre>
<p>Now, you have a fully working cluster ready. </p>
<h1>Deployment</h1>
<p>In this example, I&#8217;ll create a small container that runs a Node.js app that when run it will display &#8220;Hello from &#8221; the hostname of the container. On top of that we&#8217;ll create a load balancer, so we can see how that works.<br />
First, let&#8217;s create the container based on Node.js image. Create a file named <strong>Dockerfile </strong>with this content. </p>
<pre class="brush: bash; title: ; notranslate">
FROM node:latest
LABEL maintainer &quot;kliment@andreev.it&quot;
ADD appv1.js /app.js
ENTRYPOINT &#x5B;&quot;node&quot;, &quot;app.js&quot;]
</pre>
<p>This is our application. Save it as <strong>appv1.js</strong>.</p>
<pre class="brush: xml; title: ; notranslate">
const http = require('http');
const os = require('os');
const port = 3000;

const server = http.createServer((req, res) =&gt; {
  res.statusCode = 200;
  res.end('Hello from ' + os.hostname() + '\n');
});

server.listen(port);
</pre>
<p>Create the container. You&#8217;ll need a valid Docker Hub login. In my case, my username is klimenta. Replace it with yours.</p>
<pre class="brush: bash; title: ; notranslate">
docker build -t klimenta/appv1:latest .
</pre>
<p>It&#8217;s time to login to Docker Hub and upload the image there. You&#8217;ll be prompted for a username and password.</p>
<pre class="brush: bash; title: ; notranslate">
docker login
</pre>
<p>Upload the image.</p>
<pre class="brush: bash; title: ; notranslate">
docker push klimenta/appv1:latest
</pre>
<p>Create a Kubernetes deployment file named <strong>deployment.yaml</strong>.</p>
<pre class="brush: xml; title: ; notranslate">
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: appv1
spec:
  replicas: 3
  template:
    metadata:
      name: appv1
      labels:
        app: appv1
    spec:
      containers:
      - image: klimenta/appv1:latest
        name: nodejs
---
apiVersion: v1
kind: Service
metadata:
  name: loadbalancer
spec:
  type: LoadBalancer
  selector:
    app: appv1
  ports:
  - port: 80
    targetPort: 3000
</pre>
<p>We are creating a deployment with 3 replicas and a load balancer that listens on port 80 and sends the traffic to port 3000 on the pods with our application.<br />
Create the deployment and the load balanced service.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create -f deployment.yaml
</pre>
<p>After about 30 seconds, you&#8217;ll see that your pods are ready. </p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
appv1-596dd64666-4k7qn   1/1     Running   0          93m
appv1-596dd64666-gn5gr   1/1     Running   0          93m
appv1-596dd64666-vv9h5   1/1     Running   0          93m
</pre>
<p>The load balancer is also ready. </p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get svc
NAME           TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes     ClusterIP      10.96.0.1       &lt;none&gt;        443/TCP        102m
loadbalancer   LoadBalancer   10.111.212.41   &lt;pending&gt;     80:30310/TCP   94m
</pre>
<p>If you hit the load balancer, you&#8217;ll see a response. Replace the IP with yours.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
curl http://10.111.212.41
Hello from appv1-596dd64666-gn5gr
</pre>
<p>Now, let&#8217;s say that we created a new version of our application. Copy appv1.js as appv2.js and change  <strong>appv2.js</strong> a little bit.</p>
<pre class="brush: bash; title: ; notranslate">
cp appv1.js appv2.js
</pre>
<p>The appv2.js should look like this.</p>
<pre class="brush: xml; highlight: [7]; title: ; notranslate">
const http = require('http');
const os = require('os');
const port = 3000;

const server = http.createServer((req, res) =&gt; {
  res.statusCode = 200;
  res.end('Greetings from ' + os.hostname() + '\n');
});

server.listen(port);
</pre>
<p>Change the <strong>Dockerfile </strong>to look like this.</p>
<pre class="brush: bash; title: ; notranslate">
FROM node:latest
LABEL maintainer &quot;kliment@andreev.it&quot;
ADD appv2.js /app.js
ENTRYPOINT &#x5B;&quot;node&quot;, &quot;app.js&quot;]
</pre>
<p>Build the new image and upload it to Docker Hub.</p>
<pre class="brush: bash; title: ; notranslate">
docker build -t klimenta/appv2:latest .
docker push klimenta/appv2:latest
</pre>
<p>Deploy the new application.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl set image deployment appv1 nodejs=klimenta/appv2:latest
</pre>
<p>If you check the app now, you&#8217;ll see that it reflects the new version.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
curl http://10.111.212.41
Greetings from appv1-5d949774f5-b794k
</pre>
<p>But what if there is a bug in our application and we want to revert it back to the initial one? Easy.</p>
<pre class="brush: bash; highlight: [1,3]; title: ; notranslate">
kubectl rollout undo deployment appv1
deployment.extensions/appv1 rolled back
curl http://10.111.212.41
Hello from appv1-596dd64666-94gqh
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2019/04/centos-kubernetes-flannel-and-calico-in-a-single-master-configuration/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
