Kubernetes/Kubernetes Workshop/Step 5

From Wikitech

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…