Pitfalls of YAML and shell-less containers

Recently I stumbled across some unexpected issues setting up a simple Pod in a Kubernetes cluster.

The Origin

I tried to adapt a Pod like this one, using a Bash shell to run a single command:

apiVersion: v1
kind: Pod
metadata:
  name: my-proxy-pod
  namespace: kube-system
spec:
  containers:
    - name: proxy
      image: quay.io/kubermatic/util:2.3.0
      command:
        - /bin/bash
      args:
        - -c
        - |-
          set -euo pipefail

          echo "Starting kubectl proxy on port $PROXY_PORT..."

          kubectl proxy \
            --address=0.0.0.0 \
            --port=$PROXY_PORT \
            --accept-hosts='^.*'          
      env:
        - name: KUBECONFIG
          value: /opt/kubeconfig/kubeconfig
        - name: PROXY_PORT
          value: "8001"
      ports:
        - containerPort: 8001
          name: http
          protocol: TCP
      # yadda yadda …

Simplifying the PodSpec

I noticed that since I only run a single command, I could skip the /bin/bash entrypoint and instead use kubectl directly. My new Pod then looked like this:

#
# This Pod will not work, do not copy&paste it.
# Scroll down further on this page to find the working example.
#

apiVersion: v1
kind: Pod
metadata:
  name: my-proxy-pod
  namespace: kube-system
spec:
  containers:
    - name: proxy
      image: quay.io/kubermatic/util:2.3.0
      command:
        - kubectl
      args:
        - proxy
        - --address='0.0.0.0'
        - --accept-hosts='^.*'
      env:
        - name: KUBECONFIG
          value: /opt/kubeconfig/kubeconfig
      ports:
        - containerPort: 8001
          name: http
          protocol: TCP
      # yadda yadda …

I deployed this pod (kubectl apply --filename mypod.yaml) and tried it out, only to receive

$ curl http://localhost:8001/metrics
Forbidden

WTF is happening? Why am I suddenly not authenticated? It felt like kubectl proxy was simply ignoring my CLI flags, or that the filtering must be broken. I also wondered if the proxy sub command can simply only forward to the REST API and not any arbitrary endpoint of the Kubernetes server.

I spent an hour trying different flags. I forgot to set --accept-paths='^.*', but even with this flag added, it still wouldn’t work. The only thing to make it truly work was to add --disable-filter, but this is not what I wanted.

I even checked inside the container whether or not the flags were not properly propagated to kubectl proxy, but ps auxf clearly showed

Terminal screenshot showing the output of `ps`

Looks sane, right? But it still wouldn’t work.

The Solution

Long story short: quotes. Without a shell wrapper, Go (kubectl) has to parse the flag values as '...' and Go will simply not strip the single quotes. Meaning that my --accept-paths flag defined a Regex that would never ever match anything.

So to solve this, simply remove the quotes or quote differnently, like so:

apiVersion: v1
kind: Pod
metadata:
  name: my-proxy-pod
  namespace: kube-system
spec:
  containers:
    - name: proxy
      image: quay.io/kubermatic/util:2.3.0
      command:
        - kubectl
      args:
        - proxy
        - "--address=0.0.0.0"
        - "--accept-hosts=^.*"
      env:
        - name: KUBECONFIG
          value: /opt/kubeconfig/kubeconfig
      ports:
        - containerPort: 8001
          name: http
          protocol: TCP
      # yadda yadda …

Og der går du – now the flag values are parsed correctly, kubectl understands what I meant and the proxy works like expected.

Another valid approach would have been

      args:
        - proxy
        - --address
        - 0.0.0.0
        - --accept-hosts
        - ^.*

The Lesson

If you’re not using a shell anymore, pay attention to how strings are quoted and make sure you’re not getting stray quotes anywhere. But at the same time, make sure your YAML is still quoted correctly.

There’s really no good way to debug this, unless the underlying application (kubectl in this example) would explicitly print the value it understood for every single CLI flag. Something I have rarely seen apps do.