Files
kodekloud-engineer/kubernetes/level 3/task-2.md

9.6 KiB

Assignment

The Nautilus DevOps team wants to deploy a PHP website on a Kubernetes cluster. They plan to use Apache as the web server and MySQL for the database. The team has already gathered the requirements and now wants to make the website live. More details can be found below:

  1. Create a ConfigMap named php-config containing the data variables_order = "EGPCS" for the php.ini file.

  2. Create a Deployment named lamp-wp.

  3. Within this Deployment, create two containers. The first container should be named httpd-php-container and utilize the image webdevops/php-apache:alpine-3-php7. The second container should be named mysql-container and use the image mysql:5.6. Mount the php-config ConfigMap in the httpd container at the location /opt/docker/etc/php/php.ini.

  4. Note that secrets have already been created for the following MySQL-related values: MySQL root password, MySQL user, MySQL password, MySQL host, and MySQL database. These secrets are securely stored and can be accessed as needed.

  5. Add the following environment variables for both containers: MYSQL_ROOT_PASSWORD, MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD, and MYSQL_HOST. Ensure that their values are sourced from the secrets created earlier. Please utilize the env field (do not use envFrom) to define the name-value pairs of the environment variables.

  6. Create a NodePort type Service named lamp-service to expose the web application, specifying the NodePort as 30008.

  7. Create a Service for MySQL named mysql-service, ensuring its port is set to 3306.

  8. A file named /tmp/index.php is available on the jump-host.

a) Copy this file into the httpd container under the Apache document root at /app, replacing the dummy values for MySQL-related variables with the corresponding environment variables you have defined. Ensure that the MySQL-related details are not hardcoded in this file, and utilize environment variables to retrieve those values.

b) You should be able to access the index.php file through NodePort 30008. Upon accessing this page, the message Connected successfully should be displayed.

Note: The kubectl utility on the jump-host has been configured to work with the Kubernetes cluster.

Solution

Build LAMP WordPress Stack — lamp-wp + php-config + services

A two-container LAMP pod (Apache/PHP + MySQL), a ConfigMap for php.ini, two Services, and a PHP page wired to the DB via env vars. Manifests applied via a multi-document heredoc, then the file copied in.

Step 0 — Confirm the secret names and keys (do this first)

The env vars source from pre-created Secrets. Verify their exact names/keys and adjust the manifest if yours differ:

kubectl get secrets
for s in mysql-root-pass mysql-user-pass mysql-host mysql-db-url; do
  echo "== $s =="; kubectl get secret "$s" -o jsonpath='{.data}' | tr ',' '\n'
done

This solution uses the standard mapping:

Env var Secret name Key
MYSQL_ROOT_PASSWORD mysql-root-pass password
MYSQL_DATABASE mysql-db-url database
MYSQL_USER mysql-user-pass username
MYSQL_PASSWORD mysql-user-pass password
MYSQL_HOST mysql-host host

Step 1 — Apply ConfigMap + Deployment + Services (heredoc)

kubectl apply -f - <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
  name: php-config
data:
  php.ini: |
    variables_order = "EGPCS"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: lamp-wp
  labels:
    app: lamp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: lamp
      tier: frontend
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: lamp
        tier: frontend
    spec:
      containers:
        - name: httpd-php-container
          image: webdevops/php-apache:alpine-3-php7
          ports:
            - containerPort: 80
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef: { name: mysql-root-pass, key: password }
            - name: MYSQL_DATABASE
              valueFrom:
                secretKeyRef: { name: mysql-db-url, key: database }
            - name: MYSQL_USER
              valueFrom:
                secretKeyRef: { name: mysql-user-pass, key: username }
            - name: MYSQL_PASSWORD
              valueFrom:
                secretKeyRef: { name: mysql-user-pass, key: password }
            - name: MYSQL_HOST
              valueFrom:
                secretKeyRef: { name: mysql-host, key: host }
          volumeMounts:
            - name: php-config-volume
              mountPath: /opt/docker/etc/php/php.ini
              subPath: php.ini
        - name: mysql-container
          image: mysql:5.6
          ports:
            - containerPort: 3306
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef: { name: mysql-root-pass, key: password }
            - name: MYSQL_DATABASE
              valueFrom:
                secretKeyRef: { name: mysql-db-url, key: database }
            - name: MYSQL_USER
              valueFrom:
                secretKeyRef: { name: mysql-user-pass, key: username }
            - name: MYSQL_PASSWORD
              valueFrom:
                secretKeyRef: { name: mysql-user-pass, key: password }
            - name: MYSQL_HOST
              valueFrom:
                secretKeyRef: { name: mysql-host, key: host }
      volumes:
        - name: php-config-volume
          configMap:
            name: php-config
---
apiVersion: v1
kind: Service
metadata:
  name: lamp-service
  labels:
    app: lamp
spec:
  type: NodePort
  selector:
    app: lamp
    tier: frontend
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30008
---
apiVersion: v1
kind: Service
metadata:
  name: mysql-service
  labels:
    app: lamp
spec:
  selector:
    app: lamp
    tier: frontend
  ports:
    - port: 3306
      targetPort: 3306
EOF

kubectl rollout status deployment/lamp-wp

Step 2 — Point index.php at the env vars (no hardcoded DB values)

Edit /tmp/index.php on the jump-host so the MySQL values come from getenv(...) instead of dummy literals. The corrected connection block:

<?php
$dbname = getenv('MYSQL_DATABASE');
$dbuser = getenv('MYSQL_USER');
$dbpass = getenv('MYSQL_PASSWORD');
$dbhost = getenv('MYSQL_HOST');

$conn = mysqli_connect($dbhost, $dbuser, $dbpass, $dbname);
if (!$conn) {
    die("Connection failed: " . mysqli_connect_error());
}
echo "Connected successfully";
?>

Keep the rest of the file as-is; only swap the four dummy assignments for the getenv() calls.

Step 3 — Copy the file into the httpd container's document root (/app)

POD=$(kubectl get pod -l app=lamp -o jsonpath='{.items[0].metadata.name}')

kubectl cp /tmp/index.php "$POD":/app/index.php -c httpd-php-container

How it works

ConfigMap → php.ini via subPath

php-config holds one key, php.ini, whose value is the directive variables_order = "EGPCS". It's mounted into the httpd container with subPath: php.ini at /opt/docker/etc/php/php.ini. subPath mounts a single file into an existing directory rather than replacing the whole directory — essential here, since /opt/docker/etc/php/ contains other files that must stay. Without subPath, the mount would hide everything else in that directory.

Two containers, one pod

Apache/PHP and MySQL run as two containers in the same pod, sharing the pod's network namespace. Both get the identical five env vars, each sourced from a Secret via valueFrom.secretKeyRef — using the env field per the requirement (not envFrom, which would bulk-import a whole Secret under its own key names). The MySQL container reads MYSQL_* to initialize the database and user; the httpd container reads the same values so PHP can connect with matching credentials.

The two Services

  • lamp-serviceNodePort, targetPort: 80 (Apache's port), nodePort: 30008. This is the public entry point: <node-ip>:30008 → Apache :80.
  • mysql-service — default ClusterIP, port: 3306. It gives MySQL a stable in-cluster DNS name (mysql-service) so the app can reach the DB by name. Both Services select the same pod (app: lamp, tier: frontend); each routes to the port relevant to its container.

Whatever the mysql-host Secret contains (e.g. mysql-service or localhost) is what MYSQL_HOST/$dbhost becomes — so the app connects through the correct host without hardcoding it.

Why the PHP file uses getenv()

Requirement 8a forbids hardcoding DB details. getenv('MYSQL_HOST') etc. pull the values from the container's environment (the Secret-sourced env vars) at request time. So credentials live only in Secrets and env — never in the file — and the page renders Connected successfully once the connection succeeds. The webdevops php-apache image serves from /app, so index.php copied there is reachable at the web root.

Verify

# Pod running with both containers
kubectl get pods -l app=lamp                       # READY 2/2

# php.ini mounted correctly
kubectl exec "$POD" -c httpd-php-container -- cat /opt/docker/etc/php/php.ini

# Services present
kubectl get svc lamp-service mysql-service

# The page returns the success message
curl -s http://<node-ip>:30008/index.php

Expected — pod READY 2/2, php.ini showing variables_order = "EGPCS", both services present, and the page printing Connected successfully at <node-ip>:30008/index.php.

If it shows a connection error instead, MySQL 5.6 may still be initializing (first boot is slow) — retry after a minute. If it persists, re-check that the Secret names/keys in Step 0 match the manifest and that mysql-host resolves to a reachable host.