As of Kubernetes 1.3, DNS is a built-in
service launched automatically using the addon manager.
The addons are in the /etc/kubernetes/addons
directory on the master node.
The service can be used within pods to find other services running on the same cluster.
Multiple containers within 1 pod don't need this service, as they can contact each other directly. A container in the same pod can just use localhost:port
.
To make DNS work, a pod will need a service definition
.
How can app 1 reach app 2 using DNS? The container itself can talk to the service of App 2.
If you ran the host for app1-service
and got back 10.0.0.1, host app2-service
could get back 10.0.0.2.
Examples from the CL
host app1-service # has addr 10.0.0.1 host app2-service # has addr 10.0.0.2 host app2-service.default # app2-service.default has address 10.0.0.2 host app2-service.default.svc.cluster.local # app2-service.default.svc.cluster.local has addr 10.0.0.2
The default
stands for default namespace. Pods and services can be launched in different namespaces (to logically seperate your cluster).
So how does this resolution work?
Say we have a pod and we run kubectl run -i -tty busybox --image=busybox --restart=Never -- sh
and the from the shell run cat /etc/resolv.conf
, can can see that there will be a nameserver
. If you do a lookup of the service name in this folder, you'll see why the above works with .default
and .default.svc.whatever
.
After creating a secrets type, pod type for a database (SQL using the secrets), and a service for exposing certain ports for the database and then deploying three replicas for a helloworld-deployment
that also has a index-db.js
file which we run node index-db.js
which will have code that works on the service. The value of the MYSQL_HOST
being set to database-service
will resolve with the database-service.yml file where the metadata name
is database-service
.
Running kubectl get pod
we should see the database plus 3 pods running for the deployment.
Running kubectl logs [deployment-name]
will also show us the logs for that pod.
Again, remember that running kubectl get svc
will get all the services available.
Config params that are not secret can be put in the ConfigMap.
The input is again key-value pairs.
The ConfigMap
key-value pairs can then be read by the app using:
It can also contain full config files eg. a webserver config file. Then that file can then be mounted using volumes where the application expects its config file.
This was you can inject
config settings into containers without changing the container itself.
To generate a configmap using files:
$ cat << EOF > app.properties driver=jdbc database=postgres lookandfeel=1 otherparams=xyz param.with.hierarchy=xyz EOF $ kubectl create configmap app-config --from-file=app.properties
How to use it? You can create a pod that exposes the ConfigMap using a volume.
# pod-helloworld.yml w/ secrets apiVersion: v1 kind: Pod metadata: name: nodehelloworld.example.com labels: app: helloworld spec: # The containers are listed here containers: - name: k8s-demo image: okeeffed/docker-demo ports: - containerPort: 3000 # @@@ This are the envs in a volume mount volumeMounts: - name: credvolume mountPath: /etc/creds readOnly: true # @@@ For the ConfigMap - name: config-volume mountPath: /etc/config volumes: - name: credvolume secret: secretName: db-secrets # @@@ For the ConfigMap - name: config-volume configMap: name: app-config
From /etc/config
, the config values will be stored in files at /etc/config/driver
and /etc/config/param/with/hierarchy
.
This is an example of a pod that exposes the ConfigMap as env variables:
# pod-helloworld.yml w/ secrets apiVersion: v1 kind: Pod metadata: name: nodehelloworld.example.com labels: app: helloworld spec: # The containers are listed here containers: - name: k8s-demo image: okeeffed/docker-demo ports: - containerPort: 3000 # @@@ This are the envs in a volume mount env: - name: DRIVER valueFrom: # where you get the value from configMapKeyRef: # ensuring the ref comes from the configMap name: app-config key: driver - name: DATABASE [ ... ]
Using an example for a reverse proxy config for NGINX:
server { listen 80; server_name localhost; location / { proxy_bind 127.0.0.1; proxy_pass http://127.0.0.1:3000; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }
We could then create this config map with kubectl create configmap nginx-config --from-file=reverseproxy.conf
.
# pod-helloworld.yml w/ secrets apiVersion: v1 kind: Pod metadata: name: hellonginx.example.com labels: app: hellonginx spec: # The containers are listed here containers: - name: nginx image: nginx:1.11 ports: - containerPort: 80 # @@@ The import conf stuff volumeMounts: - name: config-volume mountPath: /etc/nginx/conf.d - name: k8s-demo image: okeeffed/docker-demo ports: - containerPort: 3000 # @@@ The important mounting volumes: - name: config-volume # @@@ this is referred to above in volumeMounts configMap: name: nginx-config items: - key: reverseproxy.conf path: reverseproxy.conf
After then also creating the service, we can grab the minikube service url and use curl to get info on that request. From here, would could see that it is nginx
answer the request and transferring it to the Node port.
If we then want to jump into the nginx container to see what is going on, we can run kubectl exec -i -t helloworld-nginx -c nginx -- bash
(-c flag to specify container) and run ps x
to see the processes and we can cat /etc/nginx/conf.d/reverseproxy.conf
.
At this stage, we can enable SSL for NGINX.
Ingress a solution since Kub 1.1 that allows inbound connections to the cluster.
It's an alternative to the external LoadBalancer
and nodePorts
. It allows you to easily expose services that need to be accessible from outside to the cluster.
With ingress you can run your own ingress controller (basically a loudbalancer) within the Kub Cluster.
There are default ingress controller available, or you can write your own ingress controller.
How does it work? If you connect over 80/443 you will first hit the Ingress Controller
. You can use the NGINX controller that comes with Kubernetes. That controller will the dirrect all the traffic.
The ingress rules
could define that if you go to host-x.example.com
you go to Pod 1
etc. You can even redirect slash URLs specifically.
To create an Ingress Controller:
# ingress-controller.yml w/ secrets apiVersion: extensions/v1beta1 kind: Ingress metadata: name: helloworld-rules spec: # @@@ Setting the important rules rules: - host: helloworld-v1.example.com http: paths: - path: / backend: serviceName: helloworld-v1 servicePort: 80 - host: helloworld-v2.example.com http: paths: - path: / backend: serviceName: helloworld-v2 servicePort: 80
In the example, the ingress controller is a Replication Controller
to ensure that there is always one up and running.
After deploying, if we curl with the -H host flag with helloworld-v1.whatever.com
and v2 respectively, it would have the ingress controller route to each server.
On public cloud providers, you can use the ingress controller to reduce the cost of your LoadBalancers.
The External DNS tool will automatically create the necessary DNS records in your external DNS server (like route53).
Diagram
How can we run stateful apps?
Volumes in kubernetes allow you to store data outside of the container. So far, all the applications have been stateless for this reason. This can be done with external services like a database, caching server (eg MySQL, AWS S3).
Persistent Volumes in Kubernetes allow you to attach a volume to a container that exists even when the container stops. Volumes can be attached using different volume plugins. Eg local volume, EBS Storage etc.
With this, we can keep state. You could run a MySQL
database using persistent volumes, although this may not be ready for production yet.
The use case is that if your node stops working, the pod can be rescheduled on another node, and the volume can be attached to the new node.
To use volumes, you first need to create the volume:
aws ec2 create-volume --sze 1- --region us-east-1 --availability-zone us-east-1 --volume-type gp2
Next, we need to create a pod with a volume def:
# pod-helloworld.yml w/ secrets apiVersion: v1 kind: Pod metadata: name: hellonginx.example.com labels: app: hellonginx spec: # The containers are listed here containers: - name: k8s-demo image: okeeffed/k8s-demo volumeMounts: - name: myvolume mountPath: myvolume # @@@ The important mounting volumes: - name: myvolume # @@@ this is referred to above in volumeMounts awsElasticBlockStore: volumeID: vol-9835id
Using Vagrant for kops, we can first create a volume using the above mentioned command.
After receiving a response, you can replace the .yml pod definition config file to attach that volumeID. Once the deployment is created and deployed.
kubectl get pod
and attach kubectl exec helloworld-deployment-923id -i -t -- bash
and then run ls -ahl /myvol/
to check for volume.echo 'test' > /myvol/myvol.txt
and echo 'test 2' > /test.txt
, we know that the latter file will not persist if the pod is restarted/rescheduled.kubectl drain ip --force
we can drain the pod. Assuming this is a Replication Controller
or Deployment
, another container should spin up.exec
command and we can confirm that the /myvol/myvol.txt
is still there, although the other /test.txt
is no longer there since it was not saved to the volume.If you need to remove the ebs volume, you can run aws ec2 delete-volume --volume-id vol-[id]
.
The kubs plugins have the capability to provision storage
for you. The AWS Plugin can for instance provision storage
for you by creating the volumes in AWS before attaching them to a node.
This is done using the StorageClass
object -- this is beta for the course but should be stable soon.
To use autoprovisioing, create the following:
# storage.yml kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: standard provisioner: kubernetes.io/aws-ebs parameters: type: gp2 zone: ap-southeast-1
Next, you can create a volume claim and specify the size:
# my-volume-claim.yml kind: PersistentVolumeClaim apiVersion: v1 metadata: name: myclaim annotations: volume.beta.kubernetes.io/storage-class: "standard" spec: accessModes: - ReadWriteOnce resources: requests: storage: 8Gi
Finally, if launching a pod:
# pod-helloworld.yml w/ secrets apiVersion: v1 kind: Pod metadata: name: mypod spec: # The containers are listed here containers: - name: myfrontend image: nginx volumeMounts: - name: mypd mountPath: '/var/www/html' # @@@ The important mounting volumes: - name: mypd # @@@ this is referred to above in volumeMounts persistentVolumeClaim: claimName: myclaim # @@@ refers to my claim from the previous type definition
After declaring a StorageClass
class from a yaml file and a PersistentVolumeClaim
class.
# storage.yml kind: StorageClass apiVersion: storage.k8s.io/v1beta1 metadata: name: standard provisioner: kubernetes.io/aws-ebs parameters: type: gp2 zone: eu-west-1a
# PV Claim kind: PersistentVolumeClaim apiVersion: v1 metadata: name: db-storage annotations: volume.beta.kubernetes.io/storage-class: "standard" spec: accessModes: - ReadWriteOnce resources: requests: storage: 8Gi
There is also a simple ReplicationController for the Wordpress DB. In the spe for the container for mysql, we declare where the mountPath
will be.
apiVersion: v1 kind: ReplicationController metadata: name: wordpress-db spec: replicas: 1 selector: app: wordpress-db template: metadata: name: wordpress-db labels: app: wordpress-db spec: containers: - name: mysql image: mysql:5.7 args: - "--ignore-db-dir=lost+found" ports: - name: mysql-port containerPort: 3306 env: - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: wordpress-secrets key: db-password volumeMounts: - mountPath: "/var/lib/mysql" name: mysql-storage volumes: - name: mysql-storage persistentVolumeClaim: claimName: db-storage
Having a makeshift secrets file for secrets:
apiVersion: v1 kind: Secret metadata: name: wordpress-secrets type: Opaque data: db-password: cGFzc3dvcmQ= # random sha1 strings - change all these lines authkey: MTQ3ZDVhMTIzYmU1ZTRiMWQ1NzUyOWFlNWE2YzRjY2FhMDkyZGQ4OA== loggedinkey: MTQ3ZDVhMTIzYmU1ZTRiMWQ1NzUyOWFlNWE2YzRjY2FhMDkyZGQ4OQ== secureauthkey: MTQ3ZDVhMTIzYmU1ZTRiMWQ1NzUyOWFlNWE2YzRjY2FhMDkyZGQ5MQ== noncekey: MTQ3ZDVhMTIzYmU1ZTRiMWQ1NzUyOWFlNWE2YzRjY2FhMDkyZGQ5MA== authsalt: MTQ3ZDVhMTIzYmU1ZTRiMWQ1NzUyOWFlNWE2YzRjY2FhMDkyZGQ5Mg== secureauthsalt: MTQ3ZDVhMTIzYmU1ZTRiMWQ1NzUyOWFlNWE2YzRjY2FhMDkyZGQ5Mw== loggedinsalt: MTQ3ZDVhMTIzYmU1ZTRiMWQ1NzUyOWFlNWE2YzRjY2FhMDkyZGQ5NA== noncesalt: MTQ3ZDVhMTIzYmU1ZTRiMWQ1NzUyOWFlNWE2YzRjY2FhMDkyZGQ5NQ==
To open up the service for the port:
apiVersion: v1 kind: Service metadata: name: wordpress-db spec: ports: - port: 3306 protocol: TCP selector: app: wordpress-db type: NodePort
Opening up the web and web service:
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: wordpress-deployment spec: replicas: 2 template: metadata: labels: app: wordpress spec: containers: - name: wordpress image: wordpress:4-php7.0 # uncomment to fix perm issue, see also https://github.com/kubernetes/kubernetes/issues/2630 # command: ['bash', '-c', 'chown www-data:www-data /var/www/html/wp-content/uploads && apache2-foreground'] ports: - name: http-port containerPort: 80 env: - name: WORDPRESS_DB_PASSWORD valueFrom: secretKeyRef: name: wordpress-secrets key: db-password - name: WORDPRESS_AUTH_KEY valueFrom: secretKeyRef: name: wordpress-secrets key: authkey - name: WORDPRESS_LOGGED_IN_KEY valueFrom: secretKeyRef: name: wordpress-secrets key: loggedinkey - name: WORDPRESS_SECURE_AUTH_KEY valueFrom: secretKeyRef: name: wordpress-secrets key: secureauthkey - name: WORDPRESS_NONCE_KEY valueFrom: secretKeyRef: name: wordpress-secrets key: noncekey - name: WORDPRESS_AUTH_SALT valueFrom: secretKeyRef: name: wordpress-secrets key: authsalt - name: WORDPRESS_SECURE_AUTH_SALT valueFrom: secretKeyRef: name: wordpress-secrets key: secureauthsalt - name: WORDPRESS_LOGGED_IN_SALT valueFrom: secretKeyRef: name: wordpress-secrets key: loggedinsalt - name: WORDPRESS_NONCE_SALT valueFrom: secretKeyRef: name: wordpress-secrets key: noncesalt - name: WORDPRESS_DB_HOST value: wordpress-db volumeMounts: # shared storage for things like media - mountPath: /var/www/html/wp-content/uploads name: uploads volumes: - name: uploads nfs: server: eu-west-1a.fs-5714e89e.efs.eu-west-1.amazonaws.com path: /
apiVersion: v1 kind: Service metadata: name: wordpress spec: ports: - port: 80 targetPort: http-port protocol: TCP selector: app: wordpress type: LoadBalancer
With the AWS Commandline, you can create a file system and mount target. For the fs, run aws efs create-file-system --creation-token
and then after grabbing the file-system-id and subnet-id, you can run aws efs create-mount-target --file-system-id <id> --security-groups <sg>
. Ensure in the above nfs
volume you update the fs id.
Pod presets can inject information into pods at runtime.
Imagine you have 20 apps to deploy, all with a specific credential. You can edit the 20 specs and add the creds, or you can use presets to create 1 Preset Object, which will inject an environment variable or config file to all matching pods.
When injecting env vars and volume mounts, the Pod Preset will apply the changes to ll containers within the pod.
# PodPreset File apiVersion: settings.k8s.io/v1alpha1 kind: PodPreset metadata: name: allow-database spec: selector: matchLabels: role: frontend env: - name: DB_PORT value: '6379' volumeMounts: - mountPath: /cache name: cache-volume volumes: - name: cache-volume emptyDir: {}
# Pod file using PodPreset apiVersion: v1 kind: Pod metadata: name: website labels: app: website role: frontend spec: containers: - name: website image: nginx ports: - containerPort: 80
$ kubectl create -f pod-preset.yml $ kubectl create -f pod.yml
Stateful dist apps - new feature from Kub 1.3.
It is introduced to be able to run stateful applications
that need:
A pet set will allow your stateful app to use DNS to find out peers. One running node of the Pet Set is called a Pet
. Using Pet Sets you can run for instance 5 cassandra nodes on Kubs named cass-1 until cass-5.
The big difference is that you don't want to connect just any specific service, you want to make sure pod whatever definitely connects to another pod.
This pet set also allows order to startup and teardown of pets.
Still a lot of work for future work.
Use cases:
Since Heapster is now deprecated, you would have to use metrics-server
or an alternative like Prometheus.
Kubernetes has the possibility to autoscale pods based on metrics.
Kubernetes can autoscale Deployment, Replication Controller or ReplicaSet.
In Kubernetes 1.3 scaling based on CPU usage is possible out of the box.
It will periodically query the utilization for the targeted pods.
--horizontal-pod-autoscaler-sync-period
flag when launching the controller manager.Requires the metrics system to work.
apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: hpa-example-autoscaler spec: scaleTargetRef: apiVersion: extensions/v1beta1 kind: Deployment name: hpa-example minReplicas: 1 maxReplicas: 10 targetCPUUtilizationPercentage: 50
apiVersion: apps/v1 kind: Deployment metadata: name: <% helloworld-deployment %> spec: replicas: 2 template: metadata: labels: app: <% app_name %> spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: env operator: In values: - dev preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 # higher the weighting, the more emphasis on rule preference: matchExpressions: - key: team operator: In values: - engineering-project1 containers: - name: k8s-demo image: <% image_name %> port: - containerPort: 3000
When scheduling, Kubernetes will score every node by summarizing the weightings per node.
Two types:
The required type create rules that must be met for the pod to be scheduled, the preferred type is a "soft" type and the rules may be met.
A good use case for pod affinity is co-located pods.
Interpod Affinity and anti-affinity
Zone topology
You can use anti-affinity to make sure a pod is only scehduled once on a node.
Anti-affinity
Affinity requires a substantial amount of processor. Take this into account if you have a lot of rules.
# pod-affinity.yml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: pod-affinity-1 spec: replicas: 1 template: metadata: labels: app: pod-affinity-1 spec: containers: - name: k8s-demo image: wardviaene/k8s-demo ports: - name: nodejs-port containerPort: 3000 --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: pod-affinity-2 spec: replicas: 1 template: metadata: labels: app: pod-affinity-2 spec: affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: "app" operator: In values: - pod-affinity-1 topologyKey: "kubernetes.io/hostname" # this could be change for zoning containers: - name: redis image: redis ports: - name: redis-port containerPort: 6379
We can then check this is fine by running kubectl get pod -o wide
to see the Node the pods are running on.
As for anti-affinity:
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: pod-affinity-1 spec: replicas: 1 template: metadata: labels: app: pod-affinity-1 spec: containers: - name: k8s-demo image: wardviaene/k8s-demo ports: - name: nodejs-port containerPort: 3000 --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: pod-affinity-2 spec: replicas: 3 template: metadata: labels: app: pod-affinity-2 spec: affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: "app" operator: In values: - pod-affinity-1 topologyKey: "kubernetes.io/hostname" containers: - name: redis image: redis ports: - name: redis-port containerPort: 6379 --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: pod-affinity-3 spec: replicas: 3 template: metadata: labels: app: pod-affinity-3 spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: "app" operator: In values: - pod-affinity-1 topologyKey: "kubernetes.io/hostname" containers: - name: k8s-demo image: wardviaene/k8s-demo ports: - name: nodejs-port containerPort: 3000 --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: pod-affinity-4 spec: replicas: 1 template: metadata: labels: app: pod-affinity-4 spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: "app" operator: In values: - pod-affinity-1 - pod-affinity-3 topologyKey: "kubernetes.io/hostname" containers: - name: k8s-demo image: wardviaene/k8s-demo ports: - name: nodejs-port containerPort: 3000 ---
Resulting run with the affinity/anti-affinity
Note that there are differences between preferred and required. With preferred, you may still have the pod scheduled in events we don't necessarily want as a best case scenario.
Tolerations is the opposite of node affinity.
# To add a taint $ kubectl taint nodes node1 key=value:NoSchedule # This will make sure that no pods will be scheduled on node1 as long as they don't have a matching toleration
# tolerations.yml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: tolerations-1 spec: replicas: 3 template: metadata: labels: app: tolerations-1 spec: containers: - name: k8s-demo image: wardviaene/k8s-demo ports: - name: nodejs-port containerPort: 3000 --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: tolerations-2 spec: replicas: 3 template: metadata: labels: app: tolerations-2 spec: tolerations: - key: "type" operator: "Equal" value: "specialnode" effect: "NoSchedule" containers: - name: k8s-demo image: wardviaene/k8s-demo ports: - name: nodejs-port containerPort: 3000
# Taint a node $ kubectl taint nodes NODE-NAME type=specialnode:NoSchedule # Taint with NoExecute $ kubectl taint nodes NODE-NAME testkey=testvalue:NoExecute
An Operator is a method of packaging, deploying and managing a Kubernetes Application.
It puts operational knowledge in an application.
If you just deploy a PostgreSQL container, it'd only start the database. But if you're going to use this operator, it'll allow you to also create replicas, initiate a failover, create backups, scale.
This is an alternative to running Kubernetes that is not using kops