<p>I'm trying to deploy and run a simple PHP application that will only show a <code>Hello World</code> message through my Kubernetes cluster which is only a master node cluster, unfortunately, I can't do that.</p>

<p>I'm describing my project structure -
I have a root project directory called <code>kubernetes-test</code> and under that directory, I have 3 <code>yaml</code> files and one directory called <code>code</code> under that directory I have a PHP file called <code>index.php</code></p>

<p><strong>hello-world-service.yaml:</strong></p>

<pre><code>apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    tier: backend
spec:
  selector:
    app: nginx
    tier: backend
  type: NodePort
  ports:
  - nodePort: 30500
    port: 80
    targetPort: 80
</code></pre>

<p><strong>nginx-deployment.yaml</strong></p>

<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    tier: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
      tier: backend
  template:
    metadata:
      labels:
        app: nginx
        tier: backend
    spec:
      volumes:
      - name: code
        hostPath:
          path: /code
      - name: config
        configMap:
          name: nginx-config
          items:
          - key: config
            path: site.conf
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
        volumeMounts:
        - name: code
          mountPath: /var/www/html
        - name: config
          mountPath: /etc/nginx/conf.d
</code></pre>

<p><strong>php-deployment.yaml</strong></p>

<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: php
  labels:
    tier: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: php
      tier: backend
  template:
    metadata:
      labels:
        app: php
        tier: backend
    spec:
      volumes:
      - name: code
        hostPath:
          path: /code
      containers:
      - name: php
        image: php:7-fpm
        volumeMounts:
        - name: code
          mountPath: /var/www/html
</code></pre>

<p><strong>code/index.php</strong></p>

<pre><code>&lt;?php
  echo 'Hello World';
</code></pre>

<p>Above all those things I've found through the internet.</p>

<p>When I ran this command <code>kubectl get pods</code>
then the status is showing <code>ContainerCreating</code> forever for the Nginx deployment like this</p>

<pre><code>NAME                    READY   STATUS              RESTARTS   AGE
nginx-64c9df788f-jxwzx   0/1     ContainerCreating    0          12h
php-55f974bb4-qvv9x      1/1     Running              0          25s
</code></pre>

<p><strong>Command:</strong> <code>kubectl describe pod nginx-64c9df788f-jxwzx</code></p>

<p><strong>Output:</strong></p>

<pre><code>Name:           nginx-64c9df788f-jxwzx
Namespace:      default
Priority:       0
Node:           bablu-node/192.168.43.123
Start Time:     Mon, 11 May 2020 03:20:58 +0600
Labels:         app=nginx
                pod-template-hash=64c9df788f
                tier=backend
Annotations:    &lt;none&gt;
Status:         Pending
IP:             
IPs:            &lt;none&gt;
Controlled By:  ReplicaSet/nginx-64c9df788f
Containers:
  nginx:
    Container ID:   
    Image:          nginx
    Image ID:       
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Waiting
      Reason:       ContainerCreating
    Ready:          False
    Restart Count:  0
    Environment:    &lt;none&gt;
    Mounts:
      /etc/nginx/conf.d from config (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-l2zp2 (ro)
      /var/www/html from code (rw)
Conditions:
  Type              Status
  Initialized       True 
  Ready             False 
  ContainersReady   False 
  PodScheduled      True 
Volumes:
  code:
    Type:          HostPath (bare host directory volume)
    Path:          /code
    HostPathType:  
  config:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      nginx-config
    Optional:  false
  default-token-l2zp2:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-l2zp2
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  &lt;none&gt;
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type     Reason       Age                    From                 Message
  ----     ------       ----                   ----                 -------
  Warning  FailedMount  31m (x14 over 147m)    kubelet, bablu-node  Unable to attach or mount volumes: unmounted volumes=[config], unattached volumes=[default-token-l2zp2 code config]: timed out waiting for the condition
  Warning  FailedMount  16m (x82 over 167m)    kubelet, bablu-node  MountVolume.SetUp failed for volume "config" : configmap "nginx-config" not found
  Warning  FailedMount  6m53s (x44 over 165m)  kubelet, bablu-node  Unable to attach or mount volumes: unmounted volumes=[config], unattached volumes=[code config default-token-l2zp2]: timed out waiting for the condition
  Warning  FailedMount  2m23s (x10 over 163m)  kubelet, bablu-node  Unable to attach or mount volumes: unmounted volumes=[config], unattached volumes=[config default-token-l2zp2 code]: timed out waiting for the condition
</code></pre>

<p>Command: <code>kubectl get events -n default</code></p>

<p><strong>Output:</strong></p>

<pre><code>LAST SEEN   TYPE      REASON              OBJECT                       MESSAGE
18m         Warning   FailedMount         pod/nginx-64c9df788f-jxwzx   MountVolume.SetUp failed for volume "config" : configmap "nginx-config" not found
8m45s       Warning   FailedMount         pod/nginx-64c9df788f-jxwzx   Unable to attach or mount volumes: unmounted volumes=[config], unattached volumes=[code config default-token-l2zp2]: timed out waiting for the condition
4m15s       Warning   FailedMount         pod/nginx-64c9df788f-jxwzx   Unable to attach or mount volumes: unmounted volumes=[config], unattached volumes=[config default-token-l2zp2 code]: timed out waiting for the condition
33m         Warning   FailedMount         pod/nginx-64c9df788f-jxwzx   Unable to attach or mount volumes: unmounted volumes=[config], unattached volumes=[default-token-l2zp2 code config]: timed out waiting for the condition
18m         Normal    Scheduled           pod/php-55f974bb4-qvv9x      Successfully assigned default/php-55f974bb4-qvv9x to bablu-node
18m         Normal    Pulled              pod/php-55f974bb4-qvv9x      Container image "php:7-fpm" already present on machine
18m         Normal    Created             pod/php-55f974bb4-qvv9x      Created container php
18m         Normal    Started             pod/php-55f974bb4-qvv9x      Started container php
18m         Normal    SuccessfulCreate    replicaset/php-55f974bb4     Created pod: php-55f974bb4-qvv9x
18m         Normal    ScalingReplicaSet   deployment/php               Scaled up replica set php-55f974bb4 to 1
</code></pre>

<p>Can anyone please help me?
Thanks in advance!!</p>

Bablu Ahmed
<p>I ran your environment and here are the main issues I found:</p>

<ul>
<li>First of all, you don't have the nginx-config deployed, but this would be only your first issue and easily addressable (more on the example below).</li>
<li>The second (and on my opinion the main) issue, is the usage of <code>HostPath</code>:

<ul>
<li>As I explained <a href="https://stackoverflow.com/questions/61853851/the-mountpath-should-be-absolute-in-kubernetes-is-it/61872581#61872581">here</a> <code>HostPath</code> requires that the container process run as root.</li>
<li>php-fpm runs as <code>www-data</code> therefore he cannot use the mount files at <code>/code</code> if this folder is mounted through <code>hostPath</code>.</li>
</ul></li>
</ul>

<p>From here our options now are:</p>

<ul>
<li>Bake the php file inside the image (or as a configmap) and run both nginx and php in the same pod (sharing an emptydir folder), more about this process in this guide: <a href="https://matthewpalmer.net/kubernetes-app-developer/articles/php-fpm-nginx-kubernetes.html" rel="nofollow noreferrer">PHP-FPM, Nginx, Kubernetes, and Docker</a> - while in one hand it involves creating a new Docker image, on the other hand it spare you of configuring a storage provisioner if you don't already have one.</li>
<li>Use an external storage to mount the file in a <a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/" rel="nofollow noreferrer">Persistent Volume</a> downloading the php file from an external repository. This approach requires nginx and php to run on the same node - because storage is RWO, meaning that can be mount as read-write on only one node. Since your setup is on a single node, I'll use this approach on this example.</li>
</ul>

<hr>

<p>I tried to reproduce as close as your example, but I had to do some changes.
Here are the files:</p>

<ul>
<li><code>cm-nginx.yaml</code>:</li>
</ul>

<pre><code>apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config
  labels:
    tier: backend
data:
  config : |
    server {
      index index.php index.html;
      error_log  /var/log/nginx/error.log;
      access_log /var/log/nginx/access.log;

      root /code;

      location / {
          try_files $uri $uri/ /index.php?$query_string;
      }

      location ~ \.php$ {
          try_files $uri =404;
          fastcgi_split_path_info ^(.+\.php)(/.+)$;
          fastcgi_pass php:9000;
          fastcgi_index index.php;
          include fastcgi_params;
          fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
          fastcgi_param PATH_INFO $fastcgi_path_info;
        }
    }
</code></pre>

<ul>
<li><code>root /code</code> is pointing the directory where to look for <code>index.php</code></li>
<li><code>fastcgi_pass php:9000</code> pointing to the service called <code>php</code> service listening on <code>port 9000</code>.</li>
</ul>

<hr>

<ul>
<li>Storage:</li>
</ul>

<p>This is mutable depending on the storage type you are using. Minikube comes with storage provider and storageclass configured out of the box. And although minikube storage provider is called <code>minikube-hostpath</code> it's a CSI that will not require root access on container level to run.</p>

<ul>
<li>That being said, here is the pvc.yaml:</li>
</ul>

<pre><code>apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: code
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: standard
</code></pre>

<p>Note that <code>standard</code> is the name of the dynamic storage provider built in minikube. What we are doing here is to create a PVC called <code>code</code> for our app to run.</p>

<ul>
<li><code>php.yaml</code>:</li>
</ul>

<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: php
  labels:
    tier: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: php
      tier: backend
  template:
    metadata:
      labels:
        app: php
        tier: backend
    spec:
      volumes:
      - name: code
        persistentVolumeClaim:
          claimName: code
      containers:
      - name: php
        image: php:7-fpm
        volumeMounts:
        - name: code
          mountPath: /code
      initContainers:
      - name: install
        image: busybox
        volumeMounts:
        - name: code
          mountPath: /code
        command:
        - wget
        - "-O"
        - "/code/index.php"
        - https://raw.githubusercontent.com/videofalls/demo/master/index.php
</code></pre>

<ul>
<li><p>here we are using a <code>busybox</code> initContainer to wget this php file (which is identical to the one you are using) and save it inside the mounted volume <code>/code</code>.</p></li>
<li><p>PHP service <code>svc-php.yaml</code>:</p></li>
</ul>

<pre><code>apiVersion: v1
kind: Service
metadata:
  name: php
  labels:
    tier: backend
spec:
  selector:
    app: php
    tier: backend
  ports:
  - protocol: TCP
    port: 9000
</code></pre>

<ul>
<li>The Nginx deployment <code>nginx.yaml</code>:</li>
</ul>

<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    tier: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
      tier: backend
  template:
    metadata:
      labels:
        app: nginx
        tier: backend
    spec:
      volumes:
      - name: code
        persistentVolumeClaim:
          claimName: code
      - name: config
        configMap:
          name: nginx-config
          items:
          - key: config
            path: site.conf
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
        volumeMounts:
        - name: code
          mountPath: /code
        - name: config
          mountPath: /etc/nginx/conf.d
</code></pre>

<p>The key points here, is the mount of the PVC called <code>code</code> on <code>mountPath</code> <code>/code</code> and the configmap we created being monted as a file called <code>site.conf</code> inside the folder <code>/etc/nginx/conf.d</code></p>

<ul>
<li>The Nginx service <code>svc-nginx.yaml</code>:</li>
</ul>

<pre><code>apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    tier: backend
spec:
  type: NodePort
  selector:
    app: nginx
    tier: backend
  ports:
  - protocol: TCP
    port: 80
</code></pre>

<p>I'm using NodePort to ease the output test.</p>

<hr>

<p><strong>Reproduction:</strong></p>

<ul>
<li>Let's create the files: first the <code>configmap</code> and <code>pvc</code>, since they are required for the pods to start correctly, then the services and deployments:</li>
</ul>

<pre><code>$ ls
cm-nginx.yaml  nginx.yaml  php.yaml  pvc.yaml  svc-nginx.yaml  svc-php.yaml

$ kubectl apply -f cm-nginx.yaml 
configmap/nginx-config created

$ kubectl apply -f pvc.yaml 
persistentvolumeclaim/code created

$ kubectl get cm
NAME           DATA   AGE
nginx-config   1      52s

$ kubectl get pvc
NAME   STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
code   Bound    pvc-b63559a0-a306-46f2-942b-0a063bc4ab6b   1Gi        RWO            standard       17s

$ kubectl apply -f svc-php.yaml 
service/php created

$ kubectl apply -f svc-nginx.yaml 
service/nginx created

$ kubectl apply -f php.yaml 
deployment.apps/php created

$ kubectl get pods
NAME                   READY   STATUS    RESTARTS   AGE
php-69d5c956ff-8tjfn   1/1     Running   0          5s

$ kubectl apply -f nginx.yaml 
deployment.apps/nginx created

$ kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
nginx-6854dcb7db-75zxt   1/1     Running   0          4s
php-69d5c956ff-8tjfn     1/1     Running   0          22s

$ kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
nginx        NodePort    10.107.16.212   &lt;none&gt;        80:31017/TCP   41s
php          ClusterIP   10.97.237.214   &lt;none&gt;        9000/TCP       44s

$ minikube service nginx --url
http://172.17.0.2:31017

$ curl -i http://172.17.0.2:31017
HTTP/1.1 200 OK
Server: nginx/1.7.9
Date: Thu, 28 May 2020 19:04:48 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/7.4.6

Demo Test
</code></pre>

<p>Here we can see the <code>curl</code> returned <code>200 OK</code> from nginx server, powered by PHP 7 and the content of the <code>index.php</code> file.</p>

<p>I hope it helps you have a clearer understanding of this scenario.</p>

<p>If you have any question, let me know in the comments.</p>
