docs: add Kubernetes CKS study notes

This commit is contained in:
2026-08-04 23:18:45 +02:00
parent a6ee7a2b07
commit 91a1849009
57 changed files with 8313 additions and 0 deletions

View File

@@ -0,0 +1,263 @@
# 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:
```bash
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)
```bash
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
<?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`)
```bash
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
```bash
# 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.