How can I reference value from one Kubernetes resource when defining another resource

12/22/2020

I'm using GKE and Helm v3 and I'm trying to create/reserve a static IP address using ComputeAddress and then to create DNS A record with the previously reserved IP address.

Reserve IP address

apiVersion: compute.cnrm.cloud.google.com/v1beta1
kind: ComputeAddress
metadata:
  name: ip-address
  annotations:
    cnrm.cloud.google.com/project-id: project-id
spec:
  location: global

Get reserved IP address

kubectl get computeaddress ip-address -o jsonpath='{.spec.address}'

Create DNS A record

apiVersion: dns.cnrm.cloud.google.com/v1beta1
kind: DNSRecordSet
metadata:
  name: dns-record-a
  annotations:
    cnrm.cloud.google.com/project-id: project-id
spec:
  name: "{{ .Release.Name }}.example.com"
  type: "A"
  ttl: 300
  managedZoneRef:
    external: example-com
  rrdatas:
    - **IP-ADDRESS-VALUE** <----

Is there a way to reference the IP address value, created by ComputeAddress, in the DNSRecordSet resource?

Basically, I need something similar to the output values in Terraform.

Thanks!

-- Milan Ilic
google-cloud-platform
google-kubernetes-engine
kubernetes
kubernetes-helm

2 Answers

12/23/2020

Currently, there is not possible to assign different value as string (IP Address) on the field "rrdatas". So you are not able to "call" another resource like the IP Address created before. You need to put the value on format x.x.x.x

-- blueboy1115
Source: StackOverflow

12/30/2020

It's interesting that something similar exists for GKE Ingress where we can reference reserved IP address and managed SSL certificate using annotations:

annotations:
  kubernetes.io/ingress.global-static-ip-name: my-static-address

I have no idea why there is not something like this for DNSRecordSet resource. Hopefully, GKE will introduce it in the future.

Instead of running two commands, I've found a workaround by using Helm's hooks.

First, we need to define Job as post-install and post-upgrade hook which will pick up the reserved IP address when it becomes ready and then create appropriate DNSRecordSet resource with it. The script which retrieves the IP address, and manifest for DNSRecordSet are passed through ConfigMap and mounted to Pod.

apiVersion: batch/v1
kind: Job
metadata:
  name: "{{ .Release.Name }}-dns-record-set-hook"
  annotations:
    # This is what defines this resource as a hook. Without this line, the
    # job is considered part of the release.
    "helm.sh/hook": post-install,post-upgrade
    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
  template:
    metadata:
      name: "{{ .Release.Name }}-dns-record-set-hook"
    spec:
      restartPolicy: OnFailure
      containers:
        - name: post-install-job
          image: alpine:latest
          command:  ['sh', '-c', '/opt/run-kubectl-command-to-set-dns.sh']
          volumeMounts:
            - name: volume-dns-record-scripts
              mountPath: /opt
            - name: volume-writable
              mountPath: /mnt
      volumes:
        - name: volume-dns-record-scripts
          configMap:
            name: dns-record-scripts
            defaultMode: 0777
        - name: volume-writable
          emptyDir: {}

ConfigMap definition with the script and manifest file:

apiVersion: v1
kind: ConfigMap
metadata:
  creationTimestamp: null
  name: dns-record-scripts
data:
  run-kubectl-command-to-set-dns.sh: |-
    # install kubectl command
    apk add curl && \
    curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.15.1/bin/linux/amd64/kubectl && \
    chmod u+x kubectl && \
    mv kubectl /bin/kubectl

    # wait for reserved IP address to be ready
    kubectl wait --for=condition=Ready computeaddress/ip-address

    # get reserved IP address
    IP_ADDRESS=$(kubectl get computeaddress ip-address -o jsonpath='{.spec.address}')

    echo "Reserved address: $IP_ADDRESS"
  
    # update IP_ADDRESS in manifest
    sed "s/##IP_ADDRESS##/$IP_ADDRESS/g" /opt/dns-record.yml > /mnt/dns-record.yml
  
    # create DNS record
    kubectl apply -f /mnt/dns-record.yml

  dns-record.yml: |-
    apiVersion: dns.cnrm.cloud.google.com/v1beta1
    kind: DNSRecordSet
    metadata:
      name: dns-record-a
      annotations:
        cnrm.cloud.google.com/project-id: project-id
    spec:
      name: "{{ .Release.Name }}.example.com"
      type: A
      ttl: 300
      managedZoneRef:
        external: example-com
      rrdatas:
        - "##IP_ADDRESS##"

And, finally, for (default) Service Account to be able to retrieve the IP address and create/update DNSRecordSet, we need to assign some roles to it:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: dnsrecord-setter
rules:
  - apiGroups: ["compute.cnrm.cloud.google.com"]
    resources: ["computeaddresses"]
    verbs: ["get", "list"]
  - apiGroups: ["dns.cnrm.cloud.google.com"]
    resources: ["dnsrecordsets"]
    verbs: ["get", "create", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: dnsrecord-setter
subjects:
  - kind: ServiceAccount
    name: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: dnsrecord-setter
-- Milan Ilic
Source: StackOverflow