Showing posts with label kubernetes. Show all posts
Showing posts with label kubernetes. Show all posts

Thursday, 15 March 2018

Kubernetes device mapper out of space - pod stuck in ContainerCreating state

If you have a pod in ContainerCreating state, follow these steps to held determine the issue:
  • carry out a wide pod listing (kubectl get pods -o wide) to determine which node your erroneous pod is hosted on
  • ssh to the node in question
  • run (as root) journalctl --unit kubelet --no-pager | grep <your pod name>
  • In this particular case the following message was present
Mar 15 12:35:49 jupiter.stack1.com kubelet[3146]: E0315 12:35:49.215454    3146 pod_workers.go:186] Error syncing pod ec706dba-2847-11e8-b8d8-0676d5f18210 ("kube-proxy-nwp6t_kube-system(ec706dba-2847-11e8-b8d8-0676d5f18210)"), skipping: failed to "CreatePodSandbox" for "kube-proxy-nwp6t_kube-system(ec706dba-2847-11e8-b8d8-0676d5f18210)" with CreatePodSandboxError: "CreatePodSandbox for pod \"kube-proxy-nwp6t_kube-system(ec706dba-2847-11e8-b8d8-0676d5f18210)\" failed: rpc error: code = Unknown desc = failed to create a sandbox for pod \"kube-proxy-nwp6t\": Error response from daemon: devmapper: Thin Pool has 152128 free data blocks which is less than minimum required 163840 free data blocks. Create more free space in thin pool or use dm.min_free_space option to change behavior"
  • This is stating that the Docker device mapper is out of space (as set in /etc/sysconfig/docker via option --storage-opt dm.loopdatasize=40G) .
  • This can usually be resolved by removing containers that have exited, dangling images and dangling volumes using the following steps:

    • Cleanup exited processes: 
docker rm $(docker ps -q -f status=exited)
    • Cleanup dangling volumes:
docker volume rm $(docker volume ls -qf dangling=true)
    • Cleanup dangling images:
docker rmi $(docker images --filter "dangling=true" -q --no-trunc)

Now run docker info to check your Device Mapper available space (meta and data)


Hitting kube-proxy directly with curl to get container logs

When you ask the API server to retrieve pod logs (i.e. kubectl logs <pod name>, it does this be connecting to kube-proxy on port 10250 (not quite sure where this is defined yet but will find out and update this post)  on the node hosting the pod. You can check this manually with curl as follows
curl -k https://<k8s node main IP address>:10250/containerLogs/<pod namespace>/<pod name>/<container name>
The no route to host message is misleading because it implies that you have a IP routing problem, which is not the case, as you'll notice if you ping the host. The actually error is caused by the kube-proxy port being blocked by firewalld on the target hosting the pod in question. Not sure why the no route to host message is given because this is obviously not a routing issue. Allowing port 10250 on the host in question resolved the issue when I experienced this problem.
re-running the curl command above will show you the logs from the container in question (no authentication needed)
NOTE: Make sure you don't have HTTP_PROXY and/or HTTPS_PROXY env vars set on the box you're running curl from (smile)

Sunday, 4 March 2018

Kubernetes Ingress de-mysified


Just finished a weekend of being totally confused about k8s ingress. I'm glad to say that I finally managed (using my OCD powers and lots of snacks/coffee) to work out what's going on. Here's what I learned. (NOTE: I'm doing this on Minikube with no add-ons) 

The confusion I experienced was made worse by several guides that skipped over the key detail that was confusing me, i.e the ingress-controller, because they either:
- Assumed I was running on GKE which handles the ingress-controller automatically
- Assumed I had enabled ingress in Minikube as an add-on which also handles the ingress-controller automatically

Now, call me old fashioned, but when I'm learning something I like to do everything myself so that I fully understand every piece.

Oddly enough, a couple of guides were plain wrong and exposed the services they were publishing via ingress as NodePort. This really confused me because the ingress controller is inside the cluster, along with the services it's routing to and therefore can contact the services "natively" on their ClusterIP. The point here (and this sounds obvious to me now but before the penny dropped it wasn't) is that the only service that needs to be exposed to the outside world when using ingress is the service that sits in front of the ingress controller itself.

The aim of this guide is to:
- Help me not forget what I learned (always good to write things down) 
- Maybe help other out people that might find ingress confusing.

So here goes...

The diagram below set's out the target system:

Here's the yaml for each component, starting from the top down:
ingress-controller-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: ingress-controller
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    targetPort: http
    nodePort: 32000
  selector:
    app: ingress-controller
ingress-controller-deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: ingress-controller
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ingress-controller
  template:
    metadata:
      labels:
        app: ingress-controller
    spec:
      containers:
        - name: nginx-ingress-controller
          image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.11.0
          args:
            - /nginx-ingress-controller
            - --default-backend-service=$(POD_NAMESPACE)/default-backend
            - --annotations-prefix=nginx.ingress.kubernetes.io
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
          - name: http
            containerPort: 80
ingress-resource.yaml
An interesting point to note from the yaml below is that I've set the host to minikube. I originally left this blank which means that any hostname or IP specified in the URL would be acceptable. however when I did this the nginx controller kept trying to redirect me (via a 308) to https. I googled around a bit and found out that this is expected behavior for the nginx controller and that specifying a host stops this from happening. So I tried putting my minikube IP address in, kubectl complained about this stating that hostnames and not IP's had to be used in the ingress host field. This being the case I just placed an entry in my local /etc/hosts
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: cluster-ingress-rules
spec:
  rules:
  - host: minikube
    http:
      paths:
      - path: /hello-world
        backend:
          serviceName: hello-world
          servicePort: 8080
hello-world-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: hello-world
  labels:
    run: hello-world
spec:
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    run: hello-world
hello-world-deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: hello-world
  labels:
    run: hello-world
spec:
  replicas: 1
  selector:
    matchLabels:
      run: hello-world
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: hello-world
    spec:
      containers:
      - image: gcr.io/google-samples/node-hello:1.0
        name: hello-world
        ports:
        - containerPort: 8080
default-backend-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: default-backend
  labels:
    app: default-backend
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: default-backend
default-backend-deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: default-backend
  labels:
    app: default-backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: default-backend
  template:
    metadata:
      labels:
        app: default-backend
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: default-backend
        image: gcr.io/google_containers/defaultbackend:1.4
        ports:
        - containerPort: 8080
Applying this lot to my cluster results in the following:
----- Ingress ------------------------------------------------------------------
NAME                    HOSTS      ADDRESS   PORTS     AGE
cluster-ingress-rules   minikube             80        20m

----- Services -----------------------------------------------------------------
NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
default-backend      ClusterIP   10.100.69.250   <none>        80/TCP         20m
hello-world          ClusterIP   10.110.199.3    <none>        8080/TCP       20m
ingress-controller   NodePort    10.111.99.142   <none>        80:32000/TCP   20m
kubernetes           ClusterIP   10.96.0.1       <none>        443/TCP        16d

----- Deployments --------------------------------------------------------------
NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
default-backend      1         1         1            1           20m
hello-world          1         1         1            1           20m
ingress-controller   1         1         1            1           20m

----- Pods ---------------------------------------------------------------------
NAME                                  READY     STATUS    RESTARTS   AGE
default-backend-79f984dc75-2bbbf      1/1       Running   0          20m
hello-world-554998f545-wjj49          1/1       Running   0          20m
ingress-controller-5cddb96544-2k9w7   1/1       Running   0          20m

As can be seen from the above, the only service exposed to the outside world is the ingress-controller service

Running minikube service list produces the following result:
|-------------|----------------------|-----------------------------|
|  NAMESPACE  |         NAME         |             URL             |
|-------------|----------------------|-----------------------------|
| default     | default-backend      | No node port                |
| default     | hello-world          | No node port                |
| default     | ingress-controller   | http://192.168.99.100:32000 |
| default     | kubernetes           | No node port                |
| kube-system | kube-dns             | No node port                |
| kube-system | kubernetes-dashboard | http://192.168.99.100:30000 |
| kube-system | tiller-deploy        | No node port                |
|-------------|----------------------|-----------------------------|
and putting http://minikube:32000/hello-world into a browser results in the message Hello Kubernetes!

And there you have it, hopefully I haven't missed anything.


Wednesday, 28 February 2018

Kubernetes - serviceaccount authentication tokens


When pods communicate with the API server, they use a serviceaccount to authenticate. 
The token for this is defined in a secret, which is mounted as a volume into the pod in question.
The token is signed by the key specified by the API servers --service-account-key-file property

You can test this token my making a request to the API server with curl as follows:
curl -H  “Authorization: Bearer <token>"  https://<master ip addr>:6443/api/v1/namespaces/<namespace>/pods/<pod name>
You can derive the token from the relevant secret as follows:
kubectl describe  secrets <secret name> -n kube-system
The result will look similar to below:
Name:         default-token-jvrkf
Namespace:    kube-system
Labels:       
Annotations:  kubernetes.io/service-account.name=default
              kubernetes.io/service-account.uid=ba8add2d-12ab-11e8-9719-080027dd95a3

Type:  kubernetes.io/service-account-token

Data
====
ca.crt:     1066 bytes
namespace:  11 bytes
token:      eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkZWZhdWx0LXRva2VuLWp2cmtmIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImRlZmF1bHQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJiYThhZGQyZC0xMmFiLTExZTgtOTcxOS0wODAwMjdkZDk1YTMiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06ZGVmYXVsdCJ9.JYVNgnSNO-Gs4zuqjZHOQs1e4RG31c_trE5AcJXqlexM5On0SriWjb-qICS2o5_M0jNmSKED67lfAomoCsKOk7a4zlAdpkc4eVWxMXEaWemu6yxQAIcJjiaBgEpOm38FMQJKEsb1D3kdD0W0HD1MNcgY0LXxyXuWv7NToqGxbYn7QJqrEEFTM5qEx5IAAA7YeWOkXqQ35Vtg1Co1u-Ilw2Cr1GPtmCazCL0t8bpP4oi7qDr98lYmWNMo8KcPZpIBoS9Cg0bX3gONOE3Fga6Aa0e6VxLPy9VyXdezXvNtsmMtW-ryJ8fvplap2wj2JPFWCEO26DIXQSx8OX_h3ds_ug

Here's an example curl request:
curl -k -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkZWZhdWx0LXRva2VuLWp2cmtmIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImRlZmF1bHQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJiYThhZGQyZC0xMmFiLTExZTgtOTcxOS0wODAwMjdkZDk1YTMiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06ZGVmYXVsdCJ9.JYVNgnSNO-Gs4zuqjZHOQs1e4RG31c_trE5AcJXqlexM5On0SriWjb-qICS2o5_M0jNmSKED67lfAomoCsKOk7a4zlAdpkc4eVWxMXEaWemu6yxQAIcJjiaBgEpOm38FMQJKEsb1D3kdD0W0HD1MNcgY0LXxyXuWv7NToqGxbYn7QJqrEEFTM5qEx5IAAA7YeWOkXqQ35Vtg1Co1u-Ilw2Cr1GPtmCazCL0t8bpP4oi7qDr98lYmWNMo8KcPZpIBoS9Cg0bX3gONOE3Fga6Aa0e6VxLPy9VyXdezXvNtsmMtW-ryJ8fvplap2wj2JPFWCEO26DIXQSx8OX_h3ds_ug" https://192.168.99.100:8443/api/v1/namespaces/kube-system/pods/kube-dns-54cccfbdf8-xqgz6 
You can see how the pod uses this token by running kubectl describe pods kube-dns-54cccfbdf8-xqgz6 -n kube-system and checking the Mounts section in the output.
Mounts:
  /kube-dns-config from kube-dns-config (rw)
  /var/run/secrets/kubernetes.io/serviceaccount from default-token-jvrkf (ro)
It's worth noting that if you run kubectl get secrets <secret name> -n kube-system -o yaml as opposed to kubectl describe secrets <secret name> -n kube-system, the token will be base64 encoded so you'll need to pipe it through base64 --decode before you copy and paste it into your curl command

If you regenerate your cluster certificates for any reason, you'll need to regenerate these tokens. To do this just delete the secrets with kubectl, new secrets will be generated automatically. However (and this is really important and had me going for a while)  these secrets will have a different name, therefore your pods will still be trying to mount the old secrets. To resolve this you also need to delete the pods (e.g. kubectl delete pod <pod1 name> <pod2 name> <pod3 name>) -n kube-system . If the pods are managed by deployments (and this is why you should use deployments by the way) they will automatically be regenerated and will mount the new secrets automatically. 

Your best friend by far in all of this is the command journalctl -u kubelet -f. If you have a pod stuck in ContainerCreating and therefore are not able to look at that it's container logs, simply log on the node in question and run journalctl -u kubelet -f | grep <pod name> .. priceless tool 
 
NOTE: if you're using Calico networking, this new token will need to be set in /etc/cni/net.d/10-calico.conf which is in turn derived from the ConfigMap calico-config. If you edit the contents of this file directly, the changes will be overwritten by the contents of the calico-config ConfigMap at the next kubelet restart.



Friday, 9 February 2018

Kubernetes ConfigMaps

Create a configMap:
kubectl create configmap env-config --from-literal=ENV_VAR_1=FOO --from-literal=ENV_VAR_2=BAR
you can view the result with 
kubectl get  configmap env-config -o yaml
Create a pod to use this configMap
apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/busybox
      command: [ "/bin/sh", "-c", "env" ]
      envFrom:
      - configMapRef:
          name: env-config
  restartPolicy: Never
kubectl create -f test-pod.yaml
This pod will print it's env and then exit. You can view the output as follows
kubectl logs test-pod -c test-container
You should see something like...
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_SERVICE_PORT=443
HOSTNAME=test-pod
SHLVL=1
HOME=/root
EXAMPLE_SERVICE_PORT_8080_TCP_ADDR=10.100.105.233
EXAMPLE_SERVICE_SERVICE_HOST=10.100.105.233
EXAMPLE_SERVICE_PORT_8080_TCP_PORT=8080
ENV_VAR_1=FOO
ENV_VAR_2=BAR
EXAMPLE_SERVICE_PORT_8080_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
EXAMPLE_SERVICE_PORT=tcp://10.100.105.233:8080
EXAMPLE_SERVICE_SERVICE_PORT=8080
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
EXAMPLE_SERVICE_PORT_8080_TCP=tcp://10.100.105.233:8080
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
PWD=/
KUBERNETES_SERVICE_HOST=10.96.0.1