I have a couple of static websites I need to host in Kubernetes - straightforward enough, just with the minor extra challenge that the cluster has a security policy preventing pods from running as root.

First step is to create an image of my website running in Nginx that runs as a non-root user. There are a few posts online showing how to do this, in summary we need to:

  • Grant some additional permissions to the nginx user which already exists in the container.
  • Change to listen on a port > 1024, instead of port 80.
  • Remove the user directive from the default nginx configuration file. I don’t know if this is strictly necessary, but if you don’t remove it then nginx warns you that this directive is ignored.

Lets tackle the config files first - to see what the default config file looks like get a shell using docker run -it nginx bash, and then cat /etc/nginx/nginx.conf.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

Its the very top line user nginx; that that needs to be removed. While I’m here, I also need to update the port that nginx listens on. By default nginx includes the configuration from files in /etc/nginx/conf.d/ (bottom line), but in this case I think its cleaner to keep all the nginx config in a single file. Using ls /etc/nginx/conf.d/ I can see that there is only a single file default.conf being included, so I can inline this file, update the port number and clean up the commented out config to leave me with:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;

    keepalive_timeout  65;

    server {
        listen       8080;
        server_name  localhost;

        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        }

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }
    }
}

I also need to run id nginx to get the uid of the “nginx” user (101) - when specifying which user the image is going to run as it needs to be specified as a uid insted of the name so that Kubernetes can validate it against the pod security policy.

Next up, the Dockefile, the bulk of which is granting the “nginx” user some additional permissions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
FROM nginx

# First, copy in the nginx configuration file (above)
COPY nginx.conf /etc/nginx/nginx.conf

# Magic commands to grant the required permissions to the nginx user
RUN chown -R nginx:nginx /usr/share/nginx/html && \
    chmod -R 755 /usr/share/nginx/html && \
    chown -R nginx:nginx /var/cache/nginx && \
    chown -R nginx:nginx /var/log/nginx && \
    chown -R nginx:nginx /etc/nginx/conf.d

# nginx also needs access to the pid file, which needs to be created first
RUN touch /var/run/nginx.pid && \
    chown -R nginx:nginx /var/run/nginx.pid

# Run as nginx user
USER 101

# Copy the static website into the default location
COPY html /usr/share/nginx/html

Finally, to run this in Kubernetes I need to build and push this image to a container registry and create a deployment (to run the container), and an ingress resource (to access it externally):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: site
spec:
  replicas: 1
  selector:
    matchLabels:
      name: site
  template:
    metadata:
      labels:
        name: site
    spec:
      containers:
      - name: site
        image: justinpealing/site:1
        ports:
        - name: http
          containerPort: 8080

---
apiVersion: v1
kind: Service
metadata:
  name: site
  labels:
    name: site
spec:
  selector:
    name: site
  ports:
  - protocol: TCP
    port: 80
    targetPort: http

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: site
spec:
  rules:
  - host: example.com
    http:
      paths:
      - backend:
          serviceName: site
          servicePort: 80