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:
-
Create a ConfigMap named php-config containing the data variables_order = "EGPCS" for the php.ini file.
-
Create a Deployment named lamp-wp.
-
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.
-
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.
-
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.
-
Create a NodePort type Service named lamp-service to expose the web application, specifying the NodePort as 30008.
-
Create a Service for MySQL named mysql-service, ensuring its port is set to 3306.
-
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-service—NodePort,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-hostresolves to a reachable host.