How to Configure a Pod to Use a Volume for Storage on Kubernetes K8s or OpenShift OCP?
I’m going to show you a live Playbook and some simple Ansible code. I’m Luca Berton and welcome to today’s episode of Ansible Pilot.
A Container’s file system lives only as long as the Container does. So when a Container terminates and restarts, filesystem changes are lost. For more consistent storage that is independent of the Container, you can use a Volume. This is especially important for stateful applications, such as key-value stores (such as Redis) and databases.
Ansible creates Kubernetes or OpenShift service
kubernetes.core.k8s
- Manage Kubernetes (K8s) objects
Let’s talk about the Ansible module k8s
.
The full name is kubernetes.core.k8s
, which means that is part of the collection of modules of Ansible to interact with Kubernetes and Red Hat OpenShift clusters.
It manages Kubernetes (K8s) objects.
Parameters
- name string /namespace string - object name / namespace
- api_version string - “v1”
- kind string - object model
- state string - present/absent/patched
- definition string - YAML definition
- src path - path for YAML definition
- template raw - YAML template definition
- validate dictionary - validate resource definition
There is a long list of parameters of the k8s
module. Let me summarize the most used.
Most of the parameters are very generic and allow you to combine them for many use-cases.
The name
and namespace
specify object name and/or the object namespace. They are useful to create, delete, or discover an object without providing a full resource definition.
The api_version
parameter specifies the Kubernetes API version, the default is “v1” for version 1.
The kind
parameter specifies an object model.
The state
like for other modules determines if an object should be created - present
option, patched - patched
option, or deleted - absent
option.
The definition
parameter allows you to provide a valid YAML definition (string, list, or dict) for an object when creating or updating.
If you prefer to specify a file for the YAML definition, the src
parameter provides a path to a file containing a valid YAML definition of an object or objects to be created or updated.
You could also specify a YAML definition template with the template
parameter.
You might find useful also the validate
parameter in order to define how to validate the resource definition against the Kubernetes schema. Please note that requires the kubernetes-validate
python module.
Links
Playbook
How to Configure a Pod to Use a Volume for Storage on Kubernetes K8s or OpenShift OCP with Ansible Playbook. I’m going to create a Pod that runs one Container. This Pod has a Volume of type emptyDir that lasts for the life of the Pod, even if the Container terminates and restarts.
code
- ansible_playbook.yml
---
- name: k8s volume Playbook
hosts: localhost
gather_facts: false
connection: local
vars:
myproject: "volume-example"
tasks:
- name: create {{ myproject }} namespace
kubernetes.core.k8s:
kind: Namespace
name: "{{ myproject }}"
state: present
api_version: v1
- name: create k8s pod
kubernetes.core.k8s:
state: present
namespace: "{{ myproject }}"
definition:
apiVersion: v1
kind: Pod
metadata:
name: redis
spec:
containers:
- name: redis
image: redis
volumeMounts:
- name: redis-storage
mountPath: /data/redis
volumes:
- name: redis-storage
emptyDir: {}
execution
ansible-pilot $ ansible-playbook kubernetes/configure-volume.yml
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit
localhost does not match 'all'
PLAY [k8s volume Playbook] ****************************************************************************
TASK [create volume-example namespace] ************************************************************
changed: [localhost]
TASK [create k8s pod] *****************************************************************************
changed: [localhost]
PLAY RECAP ****************************************************************************************
localhost : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ansible-pilot $
idempotency
ansible-pilot $ ansible-playbook kubernetes/configure-volume.yml
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit
localhost does not match 'all'
PLAY [k8s volume Playbook] ****************************************************************************
TASK [create volume-example namespace] ************************************************************
ok: [localhost]
TASK [create k8s pod] *****************************************************************************
ok: [localhost]
PLAY RECAP ****************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ansible-pilot $
before execution
- Kubernetes (k8s)
ansible-pilot $ kubeadmin get pod --namespace=volume-example
No resources found in volume-example namespace.
ansible-pilot $
- OpenShift (OCP)
ansible-pilot $ oc login -u kubeadmin https://api.crc.testing:6443
Logged into "https://api.crc.testing:6443" as "kubeadmin" using existing credentials.
You have access to 64 projects, the list has been suppressed. You can list all projects with 'oc projects'
Using project "default".
ansible-pilot $ oc project volume-example
error: A project named "volume-example" does not exist on "https://api.crc.testing:6443".
Your projects are:
* default
[...]
ansible-pilot $ oc get pod --namespace=volume-example
No resources found in volume-example namespace.
ansible-pilot $
after execution
- Kubernetes (k8s)
ansible-pilot $ kubeadmin get pod --namespace=volume-example
NAME READY STATUS RESTARTS AGE
redis 1/1 Running 0 38s
ansible-pilot $ kubeadmin get pod --namespace=volume-example --output=yaml
apiVersion: v1
items:
- apiVersion: v1
kind: Pod
metadata:
annotations:
k8s.v1.cni.cncf.io/network-status: |-
[{
"name": "openshift-sdn",
"interface": "eth0",
"ips": [
"10.217.0.129"
],
"default": true,
"dns": {}
}]
k8s.v1.cni.cncf.io/networks-status: |-
[{
"name": "openshift-sdn",
"interface": "eth0",
"ips": [
"10.217.0.129"
],
"default": true,
"dns": {}
}]
openshift.io/scc: anyuid
creationTimestamp: "2022-04-19T14:32:03Z"
name: redis
namespace: volume-example
resourceVersion: "354824"
uid: 6726f836-e601-430a-a829-1575ede0a781
spec:
containers:
- image: redis
imagePullPolicy: Always
name: redis
resources: {}
securityContext:
capabilities:
drop:
- MKNOD
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /data/redis
name: redis-storage
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-qprq2
readOnly: true
dnsPolicy: ClusterFirst
enableServiceLinks: true
imagePullSecrets:
- name: default-dockercfg-8h4dp
nodeName: crc-8rwmc-master-0
preemptionPolicy: PreemptLowerPriority
priority: 0
restartPolicy: Always
schedulerName: default-scheduler
securityContext:
seLinuxOptions:
level: s0:c26,c25
serviceAccount: default
serviceAccountName: default
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 300
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300
volumes:
- emptyDir: {}
name: redis-storage
- name: kube-api-access-qprq2
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace
- configMap:
items:
- key: service-ca.crt
path: service-ca.crt
name: openshift-service-ca.crt
status:
conditions:
- lastProbeTime: null
lastTransitionTime: "2022-04-19T14:32:03Z"
status: "True"
type: Initialized
- lastProbeTime: null
lastTransitionTime: "2022-04-19T14:32:10Z"
status: "True"
type: Ready
- lastProbeTime: null
lastTransitionTime: "2022-04-19T14:32:10Z"
status: "True"
type: ContainersReady
- lastProbeTime: null
lastTransitionTime: "2022-04-19T14:32:03Z"
status: "True"
type: PodScheduled
containerStatuses:
- containerID: cri-o://4bffa22f092c5c7808b39af757a5df46b214e5037f891e1bee8f7bd95f1ae26b
image: docker.io/library/redis:latest
imageID: docker.io/library/redis@sha256:1b36e146475b71ee04da1ce60f201308392ff8468107f91615885d2e49536010
lastState: {}
name: redis
ready: true
restartCount: 0
started: true
state:
running:
startedAt: "2022-04-19T14:32:09Z"
hostIP: 192.168.126.11
phase: Running
podIP: 10.217.0.129
podIPs:
- ip: 10.217.0.129
qosClass: BestEffort
startTime: "2022-04-19T14:32:03Z"
kind: List
metadata:
resourceVersion: ""
selfLink: ""
ansible-pilot $
- OpenShift (OCP)
ansible-pilot $ oc project volume-example
Now using project "volume-example" on server "https://api.crc.testing:6443".
ansible-pilot $ oc get pod --namespace=volume-example
NAME READY STATUS RESTARTS AGE
redis 1/1 Running 0 38s
ansible-pilot $ oc get pod --namespace=volume-example --output=yaml
apiVersion: v1
items:
- apiVersion: v1
kind: Pod
metadata:
annotations:
k8s.v1.cni.cncf.io/network-status: |-
[{
"name": "openshift-sdn",
"interface": "eth0",
"ips": [
"10.217.0.129"
],
"default": true,
"dns": {}
}]
k8s.v1.cni.cncf.io/networks-status: |-
[{
"name": "openshift-sdn",
"interface": "eth0",
"ips": [
"10.217.0.129"
],
"default": true,
"dns": {}
}]
openshift.io/scc: anyuid
creationTimestamp: "2022-04-19T14:32:03Z"
name: redis
namespace: volume-example
resourceVersion: "354824"
uid: 6726f836-e601-430a-a829-1575ede0a781
spec:
containers:
- image: redis
imagePullPolicy: Always
name: redis
resources: {}
securityContext:
capabilities:
drop:
- MKNOD
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /data/redis
name: redis-storage
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-qprq2
readOnly: true
dnsPolicy: ClusterFirst
enableServiceLinks: true
imagePullSecrets:
- name: default-dockercfg-8h4dp
nodeName: crc-8rwmc-master-0
preemptionPolicy: PreemptLowerPriority
priority: 0
restartPolicy: Always
schedulerName: default-scheduler
securityContext:
seLinuxOptions:
level: s0:c26,c25
serviceAccount: default
serviceAccountName: default
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 300
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300
volumes:
- emptyDir: {}
name: redis-storage
- name: kube-api-access-qprq2
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace
- configMap:
items:
- key: service-ca.crt
path: service-ca.crt
name: openshift-service-ca.crt
status:
conditions:
- lastProbeTime: null
lastTransitionTime: "2022-04-19T14:32:03Z"
status: "True"
type: Initialized
- lastProbeTime: null
lastTransitionTime: "2022-04-19T14:32:10Z"
status: "True"
type: Ready
- lastProbeTime: null
lastTransitionTime: "2022-04-19T14:32:10Z"
status: "True"
type: ContainersReady
- lastProbeTime: null
lastTransitionTime: "2022-04-19T14:32:03Z"
status: "True"
type: PodScheduled
containerStatuses:
- containerID: cri-o://4bffa22f092c5c7808b39af757a5df46b214e5037f891e1bee8f7bd95f1ae26b
image: docker.io/library/redis:latest
imageID: docker.io/library/redis@sha256:1b36e146475b71ee04da1ce60f201308392ff8468107f91615885d2e49536010
lastState: {}
name: redis
ready: true
restartCount: 0
started: true
state:
running:
startedAt: "2022-04-19T14:32:09Z"
hostIP: 192.168.126.11
phase: Running
podIP: 10.217.0.129
podIPs:
- ip: 10.217.0.129
qosClass: BestEffort
startTime: "2022-04-19T14:32:03Z"
kind: List
metadata:
resourceVersion: ""
selfLink: ""
ansible-pilot $
- Kubernets redis Pod Log
1:C 19 Apr 2022 14:48:44.365 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 19 Apr 2022 14:48:44.366 # Redis version=6.2.6, bits=64, commit=00000000, modified=0, pid=1, just started
1:C 19 Apr 2022 14:48:44.366 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
1:M 19 Apr 2022 14:48:44.366 * monotonic clock: POSIX clock_gettime
1:M 19 Apr 2022 14:48:44.367 * Running mode=standalone, port=6379.
1:M 19 Apr 2022 14:48:44.367 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
1:M 19 Apr 2022 14:48:44.367 # Server initialized
1:M 19 Apr 2022 14:48:44.368 * Ready to accept connections
Conclusion
Now you know how to Configure a Pod to Use a Volume for Storage on Kubernetes K8s or OpenShift OCP with Ansible.
Subscribe to the YouTube channel, Medium, and Website, X (formerly Twitter) to not miss the next episode of the Ansible Pilot.Academy
Learn the Ansible automation technology with some real-life examples in my Udemy 300+ Lessons Video Course.
My book Ansible By Examples: 200+ Automation Examples For Linux and Windows System Administrator and DevOps
Donate
Want to keep this project going? Please donate