Kubernetes/Kubernetes Workshop/Step 5
Step 5: Configurations
In step 3 we ran a PHP/Mysql application on k8s. Let’s revisit that application and use some k8s features to improve its setup. The application has four configuration settings hardcoded that should be pulled out and come from an external source:
- Which database server to use
- the database name
- the username
- the password
K8s offers “configmaps” and “secrets” to store these kinds of values. Configmaps and secrets are YAML files that get included in deployments.
Hands-on: refactor variables/secrets from the bookapp
Let’s start with the database server and declare a config map.
bookdbappconfigmap.yaml:
kind: ConfigMap
apiVersion: v1
metadata:
name: bookdbappconfigmap
data:
database: bookdbserver
With that we can use the environment variable “database” to connect to the service that will host the bookdb database called bookdbserver. Note that we also have to change the deployment yaml file to inform about the config map usage (see the file a bit later after we added the secret as well). index.php:
<?php
// params are server, user, pass, database
$conn = new mysqli($_ENV["database"], "bookdb", "bookdbpass", "books");
// Check connection
if ($conn->connect_error) {
…
Similar for secrets, i.e. the password: bookdbappsecrets.yaml:
apiVersion: v1
kind: Secret
metadata:
name: bookdbappcredentials
type: Opaque
data:
db-password: Ym9va2RicGFzcw==
Note that the value has to be base64 encoded :) Also the key db-password (unlike the configmap scenario) cannot be used directly but has to be mapped in the deployment file first.
In the deployment file we need to tell the application about the mapping of configmaps and secrets in the spec section.
bookdbappdeployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: bookdbapp
labels:
app: bookdbapp
spec:
replicas: 1
strategy:
type: RollingUpdate
selector:
matchLabels:
app: bookdbapp
template:
metadata:
labels:
app: bookdbapp
spec:
containers:
- name: bookdbapp
image: wkandek/bookdbapp:latest
imagePullPolicy: Always
envFrom:
- configMapRef:
name: bookdbappconfigmap
env:
- name: BOOKDBPASS
valueFrom:
secretKeyRef:
name: bookdbappcredentials
key: db-password
Index.php - note we are using BOOKDBPASS mapped in the deployment file above^^^
<?php
// params are server, user, pass, database
$conn = new mysqli($_ENV["database"], "bookdb", $_ENV["BOOKDBPASS"], "books");
// Check connection
if ($conn->connect_error) {
…
I also suggest changing something in the program to know whether you are actually running the new version. I changed the line:
- echo "<HTML><BODY><TABLE>";
to:
- echo "<HTML><HEAD><TITLE>configmaps</TITLE></HEAD><BODY><TABLE>";
to include a title in the resulting HTML page.
To testout the new setup, we need to rebuild the application with the new index.php file and rerun docker build, upload it to docker hub and create the new deployment
- docker build .
- docker tag <imageid> <userid>/bookdbapp
- docker push <userid>/bookdbapp
- minikube start
- Make sure all deployments, pods, hpa, etc are deleted
- kubectl get deployments; kubectl delete deployment, etc
- kubectl get services ; kubectl get pods ; kubectl get configmaps; kubectl get secrets ; kubectl get hpa
- kubectl create -f bookdbdeployment.yaml
- kubectl create -f bookdbservice.yaml
- kubectl create -f bookdbappconfigmap.yaml
- kubectl create -f bookdbappsecrets.yaml
- kubectl create -f bookdbappdeployment.yaml
- kubectl create -f bookdbappservice.yaml
- minikube service bookdbapp --url
- And access the URL via browser or curl
- Is it working, troubleshoot yourself or look at the Bonus session below
- To see what is running and for troubleshooting
- kubectl get configmaps
- kubectl get secrets
- kubectl get pods
- kubectl logs
- kubectl get services
- kubectl get deployments
Bonus: Try replacing the database user and the database name as well. Are you putting them as configmap entries or secrets?
Bonus: a troubleshooting session
minikube service bookdbapp --url yielded 172.17.0.2:30576
curl http://172.17.0.2:30576 # shows apache default page
curl http://172.17.0.2:30576/index.php # error message
Connection failed: No such file or directory
What happened? How can we debug?
First check the logs
- kubectl get pods
let's assume bookdbapp-69b79b6f7-bmptf is the id for the container
- kubectl logs bookdbapp-69b79b6f7-bmptf
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.18.0.7. Set the 'ServerName' directive globally to suppress this message
i.e. nothing useful, we need to take a look at the apache error log next, unfortunately this setup does not redirect that log to stdout
Let's log into the pod and look at the error log file
kubectl exec -it bookdbapp-69b79b6f7-bmptf /bin/bash
root@bookdbapp-69b79b6f7-bmptf:~# tail /var/log/apache2/error.log
[Fri Sep 18 05:37:05.795586 2020] [php7:warn] [pid 13] [client 172.18.0.1:53581] PHP Warning: mysqli::__construct(): (HY000/2002): No such file or directory in /var/www/html/index.php on line 3
Ok error on line 3.
root@bookdbapp-69b79b6f7-bmptf:~# head -3 /var/www/html/index.php
<?php
// params are server, user, pass, database
$conn = new mysqli($_ENV["database"], "bookdb", $_ENV["BOOKDBPASS"], "books");
It is the mysql connection string that is somehow wrong. This worked before when it was hardcoded Let’s put that back and see if it still works. There might be no editor installed. Install your editor of choice
root@bookdbapp-69b79b6f7-bmptf:~# apt-get update; apt-get install vim
Change the connect string to the previous hardcoded values:
<?php
// params are server, user, pass, database
//$conn = new mysqli($_ENV["database"], "bookdb", $_ENV["BOOKDBPASS"], "books");
$conn = new mysqli("bookdbserver", "bookdb", "bookdbpass", "books");
Ok, that still works, i.e. curl http://172.17.0.2:30576/index.php
Does the environment contain the referenced values?
It would be nice to have a phpinfo page to check.
root@bookdbapp-69b79b6f7-bmptf:~# cat > /var/www/html/phpinfo.php
<?php phpinfo() ?>
<ctrl-d>
Now check on the setting back at the host for minikube. curl is not very good but works with html2text, links works fine to check, or a any other browser, scroll to Environment to check
$ links http://172.17.0.2:30576/phpinfo.php or
$ curl http://172.17.0.2:30576/phpinfo.php | html2text | grep database
The setting exists. Can the PHP script access it? Let's change back the connection string and add an echo of value to see what happens, we need to edit the script.
<?php
// params are server, user, pass, database
echo $_ENV["database"];
$conn = new mysqli($_ENV["database"], "bookdb", $_ENV["BOOKDBPASS"], "books");
Output: Connection failed: No such file or directory Weird, need more output to see if the variable is empty or what...
<?php
// params are server, user, pass, database
echo "before";
echo $_ENV["database"];
echo "after";
$conn = new mysqli($_ENV["database"], "bookdb", $_ENV["BOOKDBPASS"], "books");
// Check connection
Output: beforeafterConnection failed: No such file or directory
$_ENV seems empty?
Let’s google that problem, “$_ENV not accessible php 7.4” finds a reference to a setting in the php.ini file in a comment in the first link at: https://www.php.net/manual/en/reserved.variables.environment.php
- Add E to the variables_order setting
root@bookdbapp-69b79b6f7-bmptf:vi /etc/php/7.4/apache2/php.ini
; This directive determines which super global arrays are registered when PHP
; starts up. G,P,C,E & S are abbreviations for the following respective super
; globals: GET, POST, COOKIE, ENV and SERVER. There is a performance penalty
; paid for the registration of these arrays and because ENV is not as commonly
; used as the others, ENV is not recommended on productions servers. You
; can still get access to the environment variables through getenv() should you
; need to.
; Default Value: "EGPCS"
; Development Value: "GPCS"
; Production Value: "GPCS";
; http://php.net/variables-order
variables_order = "EGPCS"
Ok, test: curl http://172.17.0.2:30576/index.php
Still not working, maybe apache restart required, careful here, stopping apache ends the container, but apachectl -k graceful works.
Ok, test: curl http://172.17.0.2:30576/index.php
And it works.
Thoughts:
- How did this pass local testing?
- It might be better to use getenv() instead of $_ENV[] in the php code as it will always work…