2

I am deploying my application in a read only kubernetes cluster, so I am using volumes and volumeMounts for tmp folder for apache server. Upon start of apache server within read only pod, I am getting this error:

chown: changing ownership of '/var/lock/apache2.fm2cgWmnxk': Operation not permitted

I came across this issue Kubernetes: how to set VolumeMount user group and file permissions and tried using SecurityContext.fsGroup but still getting same issue.

Here is my deployment.yaml for reference:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: &DeploymentName abc
spec:
  replicas: 1
  selector:
    matchLabels: &appName
      app: *DeploymentName
  template:
    metadata:
      name: main
      labels:
        <<: *appName
    spec:
      securityContext:
        fsGroup: 2000
        runAsNonRoot: true
        runAsUser: 1000
        runAsGroup: 3000
        fsGroupChangePolicy: "OnRootMismatch"
      volumes:
        - name: var-lock
          emptyDir: {}
      containers:
        - name: *DeploymentName
          image: abc-image
          ports:
            - containerPort: 80
          volumeMounts:
            - mountPath: /var/lock
              name: var-lock
          readinessProbe:
              tcpSocket:
                port: 80
              initialDelaySeconds: 180
              periodSeconds: 60
          livenessProbe:
              tcpSocket:
                port: 80
              initialDelaySeconds: 300
              periodSeconds: 180
          imagePullPolicy: Always
          tty: true
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
          envFrom:
            - configMapRef:
                name: *DeploymentName
          resources:
            limits:
              cpu: 1
              memory: 2Gi
            requests:
              cpu: 1
              memory: 2Gi

---
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: &hpaName abc
spec:
  maxReplicas: 1
  minReplicas: 1
  scaleTargetRef:
    apiVersion: extensions/v1beta1
    kind: Deployment
    name: *hpaName
  targetCPUUtilizationPercentage: 60

Any help is appreciated.

David Maze
  • 130,717
  • 29
  • 175
  • 215
T Ravi Theja
  • 63
  • 1
  • 11
  • I want to reproduce your error, what do you mean by readonly cluster? do you have access to the RBAC config to see this? – Bguess Apr 14 '22 at 15:05
  • Can't you create actual volume ? instead of empty dir? – Bguess Apr 14 '22 at 15:07
  • The cluster imposes Read-only filesystem. Also, inside the pods use of sudo or root user is restricted, so I need to use user:ubuntu or 1000. Regarding volume, will there be any difference if its not empty dir? – T Ravi Theja Apr 14 '22 at 17:34
  • What do you mean "Read-only filesystem" ? The volumes are read-only or you can access some volume under which you'll be able to write? Apache will need a few write permission (such as creating a lock file), if it can't I'm not sure it can run. Can you specify the exact image you use? – Pierre B. Apr 15 '22 at 10:58

2 Answers2

3

Hello, hope you are envoying your Kubernetes journey !

I wanted to try this on my kind (Kubernetes in docker) cluster locally. So this is what I've done:

First I have setup a kind cluster locally with this configuration (info here: https://kind.sigs.k8s.io/docs/user/quick-start/):

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: so-cluster-1
nodes:
- role: control-plane
  image: kindest/node:v1.23.5
- role: control-plane
  image: kindest/node:v1.23.5
- role: control-plane
  image: kindest/node:v1.23.5
- role: worker
  image: kindest/node:v1.23.5
- role: worker
  image: kindest/node:v1.23.5
- role: worker
  image: kindest/node:v1.23.5

after this I created my cluster with this command:

kind create cluster --config=config.yaml

Next, i have created a test namespace (manifest obtained with: kubectl create ns so-tests -o yaml --dry-run):

apiVersion: v1
kind: Namespace
metadata:
  name: so-tests

From there, i got my environment setted up, so I used your deployment config and replaced the deploymentName, appName and hpaName occurences by "so-71823613" (stack-overflow and you question id), but for the test, I decided to not use the hpa config.

next, since you did not provide the image you are using for apache, I used the dockerhub image httpd:2.4.53 (https://hub.docker.com/layers/httpd/library/httpd/2.4.53/images/sha256-10ed1591781d9fdbaefaafee77067f12e833c699c84ed4e21706ccbd5229fd0a?context=explore)

again, since i dont have your configmap config, i decided to comment out the part where you get env variables from the configmap.

since the default user in httpd image is "www-data", I first deployed the pod without any securityContext just to get the id of that user:

❯ k exec -it pod/so-71823613-555d8b454-z5ks5 -- id www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Once that i knew what was the id of the www-data user, I modified the securityContext. I kept the rest of the configuration (probes, volume etc.) as you configured them, here is the manifest now:

In the configuration file, the runAsUser field specifies that for any Containers in the Pod, all processes run with user ID 33(www-data). The runAsGroup field specifies the primary group ID of 33 for all processes within any containers of the Pod. If this field is omitted, the primary group ID of the containers will be root(0). Any files created will also be owned by user 33 and group 33 when runAsGroup is specified. Since fsGroup field is specified, all processes of the container are also part of the supplementary group ID 33. The owner for volume "/var/lock" and any files created in that volume will be Group ID 33. ... fsGroupChangePolicy - fsGroupChangePolicy defines behavior for changing ownership and permission of the volume before being exposed inside a Pod. This field only applies to volume types that support fsGroup controlled ownership and permissions. This field has two possible values:

OnRootMismatch: Only change permissions and ownership if permission and ownership of root directory does not match with expected permissions of the volume. This could help shorten the time it takes to change ownership and permission of a volume. Always: Always change permission and ownership of the volume when volume is mounted.

( description from here: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/)

So, once i deployed my configuration using:

kubectl apply -f deployment.yaml
deployment.apps/so-71823613 created

I got this error:

 k logs -f pod/so-71823613-7c5b65df4d-6scg5
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 10.244.1.2. Set the 'ServerName' directive globally to suppress this message
(13)Permission denied: AH00072: make_sock: could not bind to address [::]:80
(13)Permission denied: AH00072: make_sock: could not bind to address 0.0.0.0:80
no listening sockets available, shutting down
AH00015: Unable to open logs

So, first to fix the first line error, I reconnected into the pod to fetch the httpd.conf file with:

k exec -it pod/so-71823613-555d8b454-fgjcs -- cat /usr/local/apache2/conf/httpd.conf > httpd.conf

once i get the http.conf file, I modified it, by adding:

ServerName localhost:8080

(cf https://ixnfo.com/en/solution-ah00558-apache2-could-not-reliably-determine-the-servers-fully-qualified-domain-name.html)

Then I put the new httpd.conf file into a configmap named "httpconf", and modified the deployment to mount the configmap into the right place, to replace the first one (here -> "/usr/local/apache2/conf/httpd.conf") with:

    ...
    volumeMounts:
    ...
      - name: "config"
        mountPath: "/usr/local/apache2/conf/httpd.conf"
        subPath: "httpd.conf"
  volumes:
  ...
    - name: "config"
      configMap:
        name: "httpconf"
  ...



❯ kubectl apply -f configmap.yaml -f deployment.yaml
configmap/httpconf created
deployment.apps/so-71823613 created

Then i got this error remaining:

(13)Permission denied: AH00072: make_sock: could not bind to address [::]:80

So, to fix it, I changed to listening port of apache directly into the configmap http.conf file (according to this: https://askubuntu.com/questions/338218/why-am-i-getting-permission-denied-make-sock-could-not-bind-to-address-when)

Listen 8080
ServerName localhost:8080

since I am now exposing the 8080 port, I also modified the probes and the port in consequence:

...
ports:
  - containerPort: 8080
readinessProbe:
    tcpSocket:
      port: 8080
    initialDelaySeconds: 180
    periodSeconds: 60
livenessProbe:
    tcpSocket:
      port: 8080
...

After reapplying my config I got this new error:

❯ k  logs -f pod/so-71823613-7dd7bdb66d-qtf9t
[Wed Apr 20 05:50:57.863971 2022] [core:error] [pid 1:tid 139771999915328] (13)Permission denied: AH00099: could not create /usr/local/apache2/logs/httpd.pid.KYUI5g
[Wed Apr 20 05:50:57.864061 2022] [core:error] [pid 1:tid 139771999915328] AH00100: httpd: could not log pid to file /usr/local/apache2/logs/httpd.pid

To fix that issue, i used your workaround with the emptyDir and added this:

    volumeMounts:
    ...
      - mountPath: /usr/local/apache2/logs/
        name: apache2-logs
  volumes:
  ...
    - name: apache2-logs
      emptyDir: {}

here are the manifests:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: so-71823613
spec:
  replicas: 1
  selector:
    matchLabels:
      app: so-71823613
  template:
    metadata:
      name: main
      labels:
        app: so-71823613
    spec:
      securityContext:
        fsGroup: 33
        runAsNonRoot: true
        runAsUser: 33
        runAsGroup: 33
        fsGroupChangePolicy: "OnRootMismatch"
      containers:
      - name: so-71823613
        image: httpd:2.4.53
        ports:
          - containerPort: 8080
        readinessProbe:
            tcpSocket:
              port: 8080
            initialDelaySeconds: 180
            periodSeconds: 60
        livenessProbe:
            tcpSocket:
              port: 8080
            initialDelaySeconds: 300
            periodSeconds: 180
        imagePullPolicy: Always
        tty: true
        env:
          - name: POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
        # envFrom:
        #   - configMapRef:
        #       name: so-71823613
        resources:
          limits:
            cpu: 1
            memory: 2Gi
          requests:
            cpu: 1
            memory: 2Gi
        volumeMounts:
          - mountPath: /var/lock
            name: var-lock
          - mountPath: /usr/local/apache2/logs/
            name: apache2-logs
          - name: "config"
            mountPath: "/usr/local/apache2/conf/httpd.conf"
            subPath: "httpd.conf"
      volumes:
        - name: var-lock
          emptyDir: {}
        - name: apache2-logs
          emptyDir: {}
        - name: "config"
          configMap:
            name: "httpconf"

---

apiVersion: v1
kind: ConfigMap
metadata:
  name: httpconf
data:
  httpd.conf: |
    ServerRoot "/usr/local/apache2"
    Listen 8080

    LoadModule mpm_event_module modules/mod_mpm_event.so
    LoadModule authn_file_module modules/mod_authn_file.so
    LoadModule authn_core_module modules/mod_authn_core.so
    LoadModule authz_host_module modules/mod_authz_host.so
    LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
    LoadModule authz_user_module modules/mod_authz_user.so
    LoadModule authz_core_module modules/mod_authz_core.so
    LoadModule access_compat_module modules/mod_access_compat.so
    LoadModule auth_basic_module modules/mod_auth_basic.so
    LoadModule reqtimeout_module modules/mod_reqtimeout.so
    LoadModule filter_module modules/mod_filter.so
    LoadModule mime_module modules/mod_mime.so
    LoadModule log_config_module modules/mod_log_config.so
    LoadModule env_module modules/mod_env.so
    LoadModule headers_module modules/mod_headers.so
    LoadModule setenvif_module modules/mod_setenvif.so
    LoadModule version_module modules/mod_version.so
    LoadModule unixd_module modules/mod_unixd.so
    LoadModule status_module modules/mod_status.so
    LoadModule autoindex_module modules/mod_autoindex.so

    <IfModule !mpm_prefork_module>
    </IfModule>
    <IfModule mpm_prefork_module>
    </IfModule>

    LoadModule dir_module modules/mod_dir.so
    LoadModule alias_module modules/mod_alias.so

    <IfModule unixd_module>

    User www-data
    Group www-data

    </IfModule>

    ServerAdmin you@example.com
    ServerName localhost:8080

    <Directory />
        AllowOverride none
        Require all denied
    </Directory>

    DocumentRoot "/usr/local/apache2/htdocs"
    <Directory "/usr/local/apache2/htdocs">
        Options Indexes FollowSymLinks

        AllowOverride None

        Require all granted
    </Directory>

    <IfModule dir_module>
        DirectoryIndex index.html
    </IfModule>

    <Files ".ht*">
        Require all denied
    </Files>

    ErrorLog /proc/self/fd/2

    LogLevel warn

    <IfModule log_config_module>
 
        LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
        LogFormat "%h %l %u %t \"%r\" %>s %b" common

        <IfModule logio_module>
          LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
        </IfModule>

        CustomLog /proc/self/fd/1 common

    </IfModule>

    <IfModule alias_module>


        ScriptAlias /cgi-bin/ "/usr/local/apache2/cgi-bin/"

    </IfModule>

    <IfModule cgid_module>
    </IfModule>

    <Directory "/usr/local/apache2/cgi-bin">
        AllowOverride None
        Options None
        Require all granted
    </Directory>

    <IfModule headers_module>

        RequestHeader unset Proxy early
    </IfModule>

    <IfModule mime_module>

        TypesConfig conf/mime.types

        AddType application/x-compress .Z
        AddType application/x-gzip .gz .tgz

    </IfModule>

    <IfModule proxy_html_module>
    Include conf/extra/proxy-html.conf
    </IfModule>

    <IfModule ssl_module>
    SSLRandomSeed startup builtin
    SSLRandomSeed connect builtin
    </IfModule>



# ---
# apiVersion: autoscaling/v1
# kind: HorizontalPodAutoscaler
# metadata:
#   name: so-71823613
# spec:
#   maxReplicas: 1
#   minReplicas: 1
#   scaleTargetRef:
#     apiVersion: extensions/v1beta1
#     kind: Deployment
#     name: so-71823613
#   targetCPUUtilizationPercentage: 60

after waiting the initialDelaySeconds of the probes, I finally get my pod up and running correctly:

Every 1.0s: kubectl get po,svc,cm -o wide                                                                                       DESKTOP-6PBJAOK: Wed Apr 20 03:15:02 2022

NAME                              READY   STATUS    RESTARTS   AGE     IP           NODE                   NOMINATED NODE   READINESS GATES
pod/so-71823613-897768549-mcmb4   1/1     Running   0          4m13s   10.244.4.4   so-cluster-1-worker3   <none>           <none>

NAME                         DATA   AGE
configmap/httpconf           1      4m14s

Bonus:

I then decided to expose the http deployment with a service, here is the manifest (obtained from " k expose deployment so-71823613 --port 80 --target-port 8080 --dry-run=client -o yaml":

apiVersion: v1
kind: Service
metadata:
  name: so-71823613
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    app: so-71823613

as you can see, I port-forwarded the 8080 pod port to 80 in the service (you can also use an ingress controller to expose the service outside of the cluster )

tried this on my machine:

❯ k port-forward service/so-71823613 8080:80
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Handling connection for 8080

and here is the result:

enter image description here

TADA !

To conclude, I tried to reproduce the best i could with your provided information (It was kinda cool), so if this does not work for you, it means that i need more information. Thank you for your lecture. bguess.

Bguess
  • 1,700
  • 1
  • 11
  • 24
1

Field: fsGroupChangePolicy has no effect on ephemeral volume types such as secret, configMap, and emptydir. Source: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#configure-volume-permission-and-ownership-change-policy-for-pods

Other than that I assume you just copied the example but you need to apply your own user in the yaml in required fields which is "1000" as you mentioned in the comments. Before you set that just validate ids from the running container with id command. This doc should help you a lot: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod

Akin Ozer
  • 1,001
  • 6
  • 14