You'll find here a summary to run a Kubernetes cluster using the minikube tool on your laptop. The application used is Jenkins but could be any application packaged in a Docker image (or even another container image as rkt).
This little example is still "in progress" and should be updated but that it can yet allow you to launch your first services in minikube as a local k8s cluster.
K8s is an abreviation for Kubernetes, the famous containers orchestration engine from Google. This tool is an open-source one and one of the most advanced tools to orchestrate containers. https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/
Minikube is a faciltator tool that makes it easy to run Kubernetes locally on your laptop. Minikube runs a single-node Kubernetes cluster inside a VM for users looking to try out Kubernetes or develop with it day-to-day. https://github.com/kubernetes/minikube
Keys objectives of this little test:
- step 1: run a jenkins master in Minikube
- step 2: [TO BE COMPLETED] runs jenkins slaves nodes managed by the master.
To install Minikube and pre-requisites, check this note (Ubuntu 16.04) : minikube-install-note
Now that minikube is installed with its dependencies, let's start it.
minikube start This will create a VM hosting your local K8s cluster.
By default, minikube will use VirtualBox to run its VM. To use of the KVM driver you can start your minikube each time with :
minikube start --vm-driver kvm or set it as a persistent configuration :
minikube config set vm-driver kvm (To be applied, we'll need : minikube delete before to start if you ever created a minikube)
To check your minikube status, just ask :minikube status
You can discover the minikube VM IP with :minikube ip
One of the default services that are started is a Dashboard UI to manage your local cluster. This interface is great to see easily what is running and eventually to create or modify configurations.
The next command will start the dashboard and open the default browser to its URL : minikube dashboard
This is a k8s service like another, so it's available via a dedicated port on the VM IP. My local URL with VBox: http://192.168.99.100:30000/#!/workload?namespace=default and with KVM : http://192.168.42.147:30000/#!/workload?namespace=default
When starting Minikube will also configure your environment so you can use kubectl to manage your "cluster".
Here are a few commands with kubectl:
To get cluster status and URL : kubectl cluster-info
Get services list : kubectl get svc
Get nodes list : kubectl get nodes
By default, thoses commands will show only elements from the "default" namespace. You can list existing namespaces with kubectl get namespaces or its abreviated form :
$ kubectl get ns NAME STATUS AGE default Active 7d kube-public Active 7d kube-system Active 7d By default you can see that 3 namespaces exists.
To consult elements within a specific namespace, add it to your command with -n <namespace>
$ kubectl -n kube-system get svc NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE kube-dns 10.0.0.10 <none> 53/UDP,53/TCP 7d kubernetes-dashboard 10.0.0.134 <nodes> 80:30000/TCP 7d So if you create a new namespace : kubectl create namespace jenkins Remember to add -n jenkins to check its elements.
Now that our Minikube is up we can deploy services on it. We'll first create a Jenkins Master using a docker image with the plugins we need. To do so, in kubernetes model we'll have to create a "pod" to run the "jenkins-master" container and two services to expose the Jenkins Web UI and the Jenkins discovery. We'll also need a persistent volume to persist Jenkins data when we stop our minikube cluster. To manage secrets, Kubernetes offer an internal services so we'll use it to define the jenkins credentials and allow the container to get them.
Our goal could be to get something like that : ![https://cloud.google.com/solutions/images/jenkins-kubernetes-architecture.svg]
Let's stop a bit on this concept:
The concept of a pod in Kubernetes makes it easy to tag multiple containers that are treated as a single unit of deployment. They are co-located on the same host and share the same resources, such as network, memory and storage of the node. Each pod gets a dedicated IP address that’s shared by all the containers belonging to it. That’s not all – each container running within the same pod gets the same hostname, so that they can be addressed as a unit. ... When a pod is scaled out, all the containers within it are scaled as a group. ... Simply put, a pod is just a manifest of multiple container images managed together. Ref :[https://thenewstack.io/kubernetes-way-part-one/]
To access and expose the pods managed by k8s will also create services:
Services in Kubernetes consistently maintain a well-defined endpoint for pods. The service manifest has the right labels and selectors to identify and group multiple pods that act as a microservice. For example, all the Apache web server pods running on any node of the cluster that matches the label “frontend” will become a part of the service. It’s an abstraction layer that brings >multiple pods running across the cluster under one endpoint. Ref :[https://thenewstack.io/kubernetes-way-part-one/]
Container loves stateless application but sometimes we want to persist data even if the container is removed. Different providers for "PV" are available depending of the cloud provider or infrastructure we're running on. With Minikube we will be very limited to persistant directories on the minikube VM. As PV are storage ressources, pods won't attach directly created PV but claim for available PV via a claim to get what they need.
The Minikube VM boots into a tmpfs, so most directories will not be persisted across reboots (minikube stop). However, Minikube is configured to persist files stored under the following directories in the minikube VM:
- /data
- /var/lib/localkube
- /var/lib/docker
- /tmp/hostpath_pv
- /tmp/hostpath-provisioner
For more details about Persistent volumes : https://github.com/kubernetes/minikube/blob/master/docs/persistent_volumes.md Here are descriptors examples to create PV and claim : https://myjavabytes.wordpress.com/2017/03/12/getting-a-host-path-persistent-volume-to-work-in-minikube-kubernetes-run-locally/
We can describe thoses various element to configure in YAML files. We will use the kubectl CLI to create them and of course it will be easy to version thoses in a Git repo (or any SCM).
So here are the files to begin with the configuration of our Jenkins. Let's give a look at them :
We don't have much options using Minikube, so we'll use a directory under /data with a capacity of 5Gb.
apiVersion: v1 kind: PersistentVolume metadata: name: pv-jenkins spec: accessModes: - ReadWriteOnce capacity: storage: 5Gi hostPath: path: /data/pv-jenkins/ As we see this PV is accessible in Read/Write mode but only by one container at a time.
Using minikube, the directories created on the underlying hosts are only writable by root. So you either need to run your process as root in a privileged container or modify the file permissions on the host to be able to write to a hostPath volume. That's not very acceptable and my Jenkins image will use the standard jenkins user (uid=1000) so if I don't want to go root and privileged, I need to find a way to apply this:
Connect to the minikube host :
minikube ssh and apply the rights to he directory we want to use:
$ sudo mkdir -p /data/pv-jenkins/ $ sudo chmod +gW /data/pv-jenkins/ $ sudo chown root:1000 /data/pv-jenkins/ To keep it simple, we'll make a claim that we'll precise the volumeName that match the created PV.
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: jenkins-pv-claim spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi volumeName: pv-jenkins To define our pods , we'll declare what we want to deploy (kind:Deployment). As our master will be unique, we want only one replica and we set the image we want to use to create the container from. We also declare ports that the container will expose. K8s will also set some environment variables from secrets we'll have to create soon in our cluster. We can see that we use the PV claim to mount our data volume.
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: jenkins-master spec: replicas: 1 template: metadata: labels: app: jenkins-master spec: containers: - name: jenkins-master image: vberruchon/akn_jenkins_master:0.0.3 ports: - containerPort: 8080 - containerPort: 50000 env: - name: JENKINS_PASS valueFrom: secretKeyRef: name: jenkins key: jenkins_pass - name: JENKINS_OPTS valueFrom: secretKeyRef: name: jenkins key: jenkins_options volumeMounts: - name: jenkins-home mountPath: /var/jenkins_home volumes: - name: jenkins-home persistentVolumeClaim: claimName: jenkins-pv-claim And finaly, two services are defined:
- one for the Web UI (jenkins-ui) : An externally-exposed NodePort service on port 8080 that allows pods and external users to access the Jenkins user interface. This type of service can be load balanced by an HTTP load balancer.
- another for the service discovery (jenkins-discovery) : an internal, private ClusterIP service on port 50000 that the Jenkins executors use to communicate with the Jenkins master from inside the cluster.
kind: Service apiVersion: v1 metadata: name: jenkins-ui #namespace: jenkins spec: type: NodePort selector: app: jenkins-master ports: - protocol: TCP port: 8080 targetPort: 8080 name: ui --- kind: Service apiVersion: v1 metadata: name: jenkins-discovery #namespace: jenkins spec: selector: app: jenkins-master ports: - protocol: TCP port: 50000 targetPort: 50000 name: slaves We define a secret that we'll be consumed by Jenkins as and environment variable for the container.
To create the secret with kubectl, we can create a file for each keys we need, the value will be set with the content of the file (be careful, if your last line is finished with a "\n", you can use echo -n to create the file).
kubectl create secret generic jenkins --from-file=./secrets If we want to create all elements in our YAML descriptor files, we can just use the "-f" flag of "kubectl create" command:
kubectl create -f ${K8S_YAML_DIR} The creation is quite quick but you'll probably have to wait a more for your containers to be created and started.
We can check what has been created in the dashboard or lists resources with the CLI:
- kubectl get - list resources
- kubectl describe - show detailed information about a resource
- kubectl logs - print the logs from a container in a pod
- kubectl exec - execute a command on a container in a pod
For example :
kubectl get svc kubectl get pods To determine the port to access to your Jenkins UI :
$ kubectl get svc NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE jenkins-discovery 10.0.0.52 <none> 50000/TCP 8d jenkins-ui 10.0.0.127 <nodes> 8080:31096/TCP 8d kubernetes 10.0.0.1 <none> 443/TCP 8d The port is the one mapped to the 8080 for the jenkins-ui service, here 31096. It will listen on the IP of the minikube VM (minikube ip). For me it will be : http://192.168.99.100:31096/
At first start, Jenkins will need a generated password you can get in the logs. As container logs are directly available from the kubectl CLI you just need to find the pod name:
$ kubectl get pods NAME READY STATUS RESTARTS AGE jenkins-master-647471806-w9nk6 1/1 Running 1 8d and show the logs :
$ kubectl logs <pod-name> To play or create an environment really quickly we can script all that. You'll find a little script that should create everything and let you play.
The script start minikube, create the secret and our services+pods. To start from a clean state, you can delete your minikube using minikube delete (as the VM is also deleted, you'll lost your persistant storage is they are not mounted on your VBox host)
and launch th script from the directory were are your yaml files and options :
./create_minikube_jenkins.sh You can now complete your descriptor files and test locally those changes.
Those example only build a jenkins-master pod, we could now define "slave" pods that could share the work.