Search code examples
kubernetesgoogle-cloud-platformgoogle-kubernetes-enginekubernetes-helm

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


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!


Solution

  • 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