Pim Schwippert
Pim Schwippert

Reputation: 453

Merging yaml files or variables in ansible for patching kubernetes deployments

I am trying to achieve the following using ansible. I have a kubernetes deployment file that I want to add a dynamic amount of volumes and volumeMounts to.

The approach I am currently taking is putting the parts I want to patch in a variable, then try use the combine pipe to merge the different parts together. However instead of adding the parts together like this:

      volumeMounts:
        - name: nfs-standard
          mountPath: /mnt/standard
          readOnly: true
        - name: nfs-share2
          mountPath: /mnt/share2
          readOnly: true
        - name: nfs-system
          mountPath: /mnt/system
          readOnly: true

It replaces the previous one so I end up having just one. Furthermore, when I try to combine this with the final yaml, it removes other parts.

This is how I tried solving it in ansible, this part if being looped over X amount of times depending on the amount of nfs shares

    - name: "loop over fs types"
      set_fact:
        patch:
          spec:
            template:
              spec:
                containers:
                  - name: fsmonitor
                    volumeMounts:
                      - name: "nfs-{{ NFS_NAME }}"
                        mountPath: "{{ NFS_MOUNT_PATH }}"
                        readOnly: true
                volumes:
                  - name: "nfs-{{ NFS_NAME }}"
                    nfs:
                      server: "{{ NFS_SHARE_IP[0] }}"
                      path: "{{ NFS_PATH[0] }}"


    - name:
      set_fact:
        deployment: "{{ deployment | combine(patch, recursive=True) }}"

It's missing the image and such which, when deployed to kubernetes, results in an unprocessable entity

 ok: [gitlab-deploy] => {
     "deployment": {
         "apiVersion": "apps/v1",
         "kind": "Deployment",
         "metadata": {
             "name": "fsmonitor-deploy"
         },
         "spec": {
             "template": {
                 "spec": {
                     "containers": [
                         {
                             "name": "fsmonitor",
                             "volumeMounts": [
                                 {
                                     "mountPath": "/mnt/nfs",
                                     "name": "nfs-system",
                                     "readOnly": true
                                 }
                             ]
                         }
                     ],
                     "volumes": [
                         {
                             "name": "nfs-system",
                             "nfs": {
                                 "path": "/nfs/system",
                                 "server": "XXXXXXXXX"
                             }
                         }
                     ]
                 }
             }
         }
     }
 }

Here is a part of the full deployment (the container part)

                     [
                         {
                             "image": "XXXXXXXXXXXXXXXXX",
                             "imagePullPolicy": "Always",
                             "livenessProbe": {
                                 "httpGet": {
                                     "path": "/metrics",
                                     "port": XXXX
                                 },
                                 "initialDelaySeconds": 3,
                                 "periodSeconds": 3
                             },
                             "name": "fsmonitor",
                             "ports": [
                                 {
                                     "containerPort": XXXX
                                 }
                             ],
                             "resources": {
                                 "limits": {
                                     "cpu": "700m",
                                     "memory": "700Mi"
                                 },
                                 "requests": {
                                     "cpu": "200m",
                                     "memory": "200Mi"
                                 }
                             },
                             "volumeMounts": null
                         }
                     ],

I hope someone can help me with what I am trying to do, I come from a software engineering background and wrapping my head around how ansible works is difficult at times.

Upvotes: 1

Views: 939

Answers (1)

Setanta
Setanta

Reputation: 996

I would take a different approach - create variables with the nfs details and loop over those in a Jinja template. Jinja templates work well with Kubernetes resources and will allow you to use standard syntax like for loops, if/then statements and so on to create your resource files.

While it's probably possible to do something with variables and filters it's going to be far more complex and difficult to maintain.

Lets say your tasks looks like this:

- name: List of volume mounts
  set_fact:
    volume_details:
    - name: nfs-standard
      mountPath: /mnt/standard
      readOnly: true
      server: server1
    - name: nfs-share2
      mountPath: /mnt/share2
      readOnly: true
      server: server1
    - name: nfs-system
      mountPath: /mnt/system
      readOnly: true
      server: server2

- name: Create deployment file
  template:
    src: template.j2
    dest: "/tmp/template.yml"
    mode: 0755

Create a file called deployment.j2 in the templates folder:

---
deployment:
  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: fsmonitor-deploy
  spec:
    template:
      spec:
        containers:
        - name: fsmonitor
          volumeMounts:
{% for volume in volume_details %}
          - mountPath: "{{ volume.mountPath }}"
            name: "{{ volume.name }}"
            readOnly: "{{ volume.readOnly }}"
{% endfor %}
        volumes:
{% for volume in volume_details %}
        - name: "{{ volume.name }}"
          nfs:
            path: "{{ volume.mountPath }}"
            server: "{{ volume.server }}"
{% endfor %}

After the run deployment.yml will be created:

---
deployment:
  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: fsmonitor-deploy
  spec:
    template:
      spec:
        containers:
        - name: fsmonitor
          volumeMounts:
          - mountPath: "/mnt/standard"
            name: "nfs-standard"
            readOnly: "True"
          - mountPath: "/mnt/share2"
            name: "nfs-share2"
            readOnly: "True"
          - mountPath: "/mnt/system"
            name: "nfs-system"
            readOnly: "True"
        volumes:
        - name: "nfs-standard"
          nfs:
            path: "/mnt/standard"
            server: "server1"
        - name: "nfs-share2"
          nfs:
            path: "/mnt/share2"
            server: "server1"
        - name: "nfs-system"
          nfs:
            path: "/mnt/system"
            server: "server2"

Take a look at the Jinja documentation, it's quite flexible for this kind of application.

https://jinja.palletsprojects.com/en/2.11.x/

Upvotes: 2

Related Questions