Deploy ConfigMaps and Secrets in Red Hat OpenShift Kubernetes Cluster
Tags: beginners, kubernetes, configmap, secrets
Published: 2024-01-17 20:11:15

_Applications are often written to connect to a database to read and write information.  Hardcoding database connection details, port, username and password is the most insecure way and hard to manage at scale. So, Kubernetes solves this problem via `ConfigMaps` and `Secrets`._

## Two ways to store data
1. **ConfigMap**: Used to store insensitive data such as `db-port` via an `Environment variable` or a `File` or `Volume Mounts`.
2. **Secret**: Used to store and encrypt sensitive data at rest such as `database username` or `database password`

> _**etcd** is a `data store` in Kubernetes which is used to store all created resources as objects. Resources such a `Pod`, `Deployment`, `Service`, `Ingress`, `ConfigMap` are stored in `ectcd` as plain objects. however, with `secrets`, `etcd` stored the information and `encrypts` the data at rest in `base64` encoded._ 

### 1. Deploying **ConfigMap** in Kubernetes

#### - 1.a) ConfigMap via `Env Variable`
#### - 1.b) ConfigMap via `File - Volume Mount`

--------------------------------------------------------------

#### 1.a) Deploy ConfigMap via `Env Variable`
- create `configmap.yml` file as below
```
apiVersion: v1
kind: ConfigMap
metadata: 
  name: py-app-configmap
data: 
db-port: "3306"
```
- Login to OpenShift Kubernetes cluster
```
oc login --token=sha256~asf9879SDAF987sd987sdf --server=https://api.sandbox-m3.1530.p1.openshiftapps.com:6443
```
- Deploy `Configmap`
```
$ kubectl apply -f configmap.yml
configmap/py-app-configmap created
```
- verify `ConfigMap` - `$ kubectl get cm` | `kubectl get configmap`
```
$ kubectl get configmap
NAME                       DATA   AGE
config-service-cabundle    1      2d15h
config-trusted-cabundle    1      2d15h
kube-root-ca.crt           1      2d15h
openshift-service-ca.crt   1      2d15h
py-app-configmap           1      37s
```

![configmap created on OpenShift](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ucqirvq1ojtf3lvk3m69.png)

![configmap yaml on OpenShift](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cx6ci1g20ba7xu2c90bu.png)
- Kubectl Describe ConfigMap - `$kubectl describe cm  py-app-configmap`
```
$kubectl describe cm  py-app-configmap
Name:         py-app-configmap
Namespace:    jasper475-dev
Labels:       <none>
Annotations:  <none>

Data
====
db-port:
----
3306

BinaryData
====

Events:  <none>
```
- Deploy `Pods` and appending `configMap` using `deployment.yml`
```
apiVersion: apps/v1
kind: Deployment
metadata: 
  name: sample-python-deployment
  labels: 
    app: sample-python-app
spec: 
  replicas: 5
  selector: 
    matchLabels: 
      app: sample-python-app
  template: 
    metadata: 
      labels: 
        app: sample-python-app
    spec:
      containers:
      - name: python-app
        image: jasper475/d37-k8s-services-py-django-app:v2
        env:
          - name: DB-PORT
            valueFrom: 
              configMapKeyRef:
                name: py-app-configmap
                key: db-port
        ports:
        - containerPort: 8000
```
- deployed Pods
```
kubectl apply -f deploy.yaml
deployment.apps/sample-python-deployment created
```
- get Pods and ssh into it to get env variable
```
$ kubectl get pods
NAME                                        READY   STATUS    RESTARTS   AGE
sample-python-deployment-5787bd6b9f-5n779   1/1     Running   0          69s
sample-python-deployment-5787bd6b9f-h6th2   1/1     Running   0          69s
sample-python-deployment-5787bd6b9f-sft22   1/1     Running   0          69s
sample-python-deployment-5787bd6b9f-w7s56   1/1     Running   0          69s
sample-python-deployment-5787bd6b9f-xncfd   1/1     Running   0          69s
```
- SSH into a pod 
- Command: `kubectl exec -it sample-python-deployment-5787bd6b9f-5n779 -- /bin/bash` and Search(grep) for Env variable `DB-PORT`
- Dev inside python app can retrieve via: `OS.env("DB-PORT")`

```
$ kubectl exec -it sample-python-deployment-c57769684-749ct -- /bin/bash
groups: cannot find name for group ID 1011150000
1011150000@sample-python-deployment-c57769684-749ct:/app$ ls
db.sqlite3  demo  devops  manage.py  requirements.txt
1011150000@sample-python-deployment-c57769684-749ct:/app$ env | grep DB
DB-PORT=3306
NSS_SDB_USE_CACHE=no
```
- Edit DB-PORT via configmap.yml
```
apiVersion: v1
kind: ConfigMap
metadata: 
  name: py-app-configmap
data: 
  db-port: "3307"
```
- Apply changes `kubectl apply -f configmap.yml`
```
$ kubectl apply -f configmap.yml
configmap/py-app-configmap configured
```
- Notice config map Port changed to 3307
![config map - Port 3307](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mge3x7hctj0qdvqn3sh4.png)
- SSH into the POD to get Environment variable value. Notice it is still having OLD Value. `DB-PORT=3306`
```
1011150000@sample-python-deployment-c57769684-749ct:/app$ env | grep DB
DB-PORT=3306
NSS_SDB_USE_CACHE=no
1011150000@sample-python-deployment-c57769684-749ct:/app$
```

> We cannot afford to restart pod in production - to Fix this issue we use Volume Mounts and persist config values and decouple it from pod application restarts and config changes. 

#### 1.b) Deploy ConfigMap via `File - Volume Mount`

- `deploy_volume_Mount.yaml` file

- create volume under `container` 
```
spec: 
      containers: 
      |
      |
      volumes: 
        - name: db-connection
          configMap: 
            name: py-app-configmap

```

- Mount volume: provide `volume name` and `volume path`
```
    spec:
      containers:
      - name: python-app
        image: jasper475/d37-k8s-services-py-django-app:v2
        volumeMounts:
          - name: db-connection
          mountPath: /opt
```
- apply deployment changes
```
$ kubectl apply -f deploy_volume_mount.yaml
deployment.apps/sample-python-deployment configured
```
- Get Pods `kubectl get pods`
```
$ kubectl get pods
NAME                                        READY   STATUS    RESTARTS   AGE
sample-python-deployment-7b67d95fdc-56p4g   1/1     Running   0          37s
sample-python-deployment-7b67d95fdc-5sdcx   1/1     Running   0          37s
sample-python-deployment-7b67d95fdc-5v2r8   1/1     Running   0          35s
sample-python-deployment-7b67d95fdc-74sb8   1/1     Running   0          35s
sample-python-deployment-7b67d95fdc-cs9px   1/1     Running   0          37s
```
- SSH into pod as see ENV variable: 
- Notice: There is no environment variable - because `deploy_volume_mount.yaml` file, since we removed it.
```
$ kubectl exec -it sample-python-deployment-7b67d95fdc-56p4g -- /bin/bash
1011150000@sample-python-deployment-7b67d95fdc-56p4g:/app$ env | grep DB
NSS_SDB_USE_CACHE=no
1011150000@sample-python-deployment-7b67d95fdc-56p4g:/app$
```
- Notice : volume is mounted on `/opt` path and `port: 3307`
```
1011150000@sample-python-deployment-7b67d95fdc-56p4g:/app$ ls /opt
db-port
1011150000@sample-python-deployment-7b67d95fdc-56p4g:/app$ cat /opt/db-port | more
3307
```
- Change Port to `3308` in `configmap.yml` and apply 
```
apiVersion: v1
kind: ConfigMap
metadata: 
  name: py-app-configmap
data: 
  db-port: "3308"
```
- apply changes: `Kubectl apply -f configmap.yml`
- describe configmap : `$ kubectl describe cm py-app-configmap`
```
$ kubectl describe cm py-app-configmap
Name:         py-app-configmap
Namespace:    jasper475-dev
Labels:       <none>
Annotations:  <none>

Data
====
db-port:
----
3308

BinaryData
====

Events:  <none>
```
- 
![Config Map: port 3308](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tj7vntmrvjuh4sq8g9y4.png)
- SSH into POD and get Port info - `kubectl exec -it sample-python-deployment-7b67d95fdc-56p4g -- /bin/bash`
```
$ kubectl exec -it sample-python-deployment-7b67d95fdc-56p4g -- /bin/bash
groups: cannot find name for group ID 1011150000
1011150000@sample-python-deployment-7b67d95fdc-56p4g:/app$ env | grep DB
NSS_SDB_USE_CACHE=no
1011150000@sample-python-deployment-7b67d95fdc-56p4g:/app$ ls  /opt
db-port
1011150000@sample-python-deployment-7b67d95fdc-56p4g:/app$ cat /opt/db-port | more
3308
```

### 2. Deploying **ConfigMap** in Kubernetes

- create a `Secret` via CLI command
- `kubectl create secret generic - to store passwords`
- `kubectl create secret tls - to store certificates`

```
$ kubectl create secret generic empty-secret
secret/empty-secret created

$ kubectl get secret empty-secret
NAME           TYPE     DATA   AGE
empty-secret   Opaque   0      19s
```

or 

```
$ kubectl create secret genericmy-db-secret --from-literal=db-port="3308"
```

or 

- Create secret via `basic_secret.yaml` file
```
apiVersion: v1
kind: Secret
metadata:
  name: secret-basic-auth
type: kubernetes.io/basic-auth
stringData:
  username: admin # required field for kubernetes.io/basic-auth
  password: t0p-Secret # required field for kubernetes.io/basic-auth
``` 
- apply : `kubectl apply -f basicsecret.yaml`

```
$ kubectl apply -f basic_secret.yaml                                     
secret/secret-basic-auth created
$  kubectl get secret secret-basic-auth
NAME                TYPE                       DATA   AGE
secret-basic-auth   kubernetes.io/basic-auth   2      17s
PS C:\Users\Jasper\OneDrive\Documents\CodeRepo\kubernetes\d41_ConfigMaps_Secrets> 
```
- Verify Secrets

![Secret create ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m6rskd4y152ton5exhv5.png)
- Describe `Secret`
```
kubectl describe secret secret-basic-auth
Name:         secret-basic-auth
Namespace:    jasper475-dev
Labels:       <none>
Annotations:  <none>

Type:  kubernetes.io/basic-auth

Data
====
password:  10 bytes
username:  5 bytes
```
- Edit secret: `kubectl edit secret secret-basic-auth`
```
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
  password: dDBwLVNlY3JldA==
  username: YWRtaW4=
kind: Secret
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Secret","metadata":{"annotations":{},"name":"secret-basic-auth","namespace":"jasper475-dev"},"stringData":{"password":"t0p-Secret","username":"admin"},"type":"kubernetes.io/basic-auth"}
  creationTimestamp: "2024-01-17T20:02:39Z"
  name: secret-basic-auth
  namespace: jasper475-dev
  resourceVersion: "1769551165"
  uid: 46bf94fa-c060-4f6b-83d6-9e08a4637b25
type: kubernetes.io/basic-auth
```

- Verify Secrets: By default, they are encoded `Base64`
```
Jasper@JASPERS-PC MINGW64 ~/OneDrive/Documents/CodeRepo/kubernetes/d41_ConfigMaps_Secrets
$ echo dDBwLVNlY3JldA== | base64 --decode
t0p-Secret
Jasper@JASPERS-PC MINGW64 ~/OneDrive/Documents/CodeRepo/kubernetes/d41_ConfigMaps_Secrets
$ echo YWRtaW4= | base64 --decode
admin
```

**Credits:-**
Thanks to [Abhishek Veeramalla](https://www.youtube.com/@AbhishekVeeramalla)