Repository: kinvolk/lokomotive
Pull request #386 (2020-05-04 08:47:12)

Add nodeAffinity and tolerations to contour
**Note**: See changes per commit to know the gradual changes made.

Fixes https://github.com/kinvolk/lokomotive/issues/262


Patch:
diff --git a/assets/components/contour/contour/03-contour.yaml b/assets/components/contour/contour/03-contour.yaml
deleted file mode 100644
index 5976e1b8..00000000
--- a/assets/components/contour/contour/03-contour.yaml
+++ /dev/null
@@ -1,104 +0,0 @@
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  labels:
-    app: contour
-  name: contour
-  namespace: projectcontour
-spec:
-  replicas: 2
-  strategy:
-    type: RollingUpdate
-    rollingUpdate:
-      # This value of maxSurge means that during a rolling update
-      # the new ReplicaSet will be created first.
-      maxSurge: 50%
-  selector:
-    matchLabels:
-      app: contour
-  template:
-    metadata:
-      annotations:
-        prometheus.io/scrape: "true"
-        prometheus.io/port: "8000"
-      labels:
-        app: contour
-    spec:
-      affinity:
-        podAntiAffinity:
-          preferredDuringSchedulingIgnoredDuringExecution:
-          - podAffinityTerm:
-              labelSelector:
-                matchLabels:
-                  app: contour
-              topologyKey: kubernetes.io/hostname
-            weight: 100
-      containers:
-      - args:
-        - serve
-        - --incluster
-        - --xds-address=0.0.0.0
-        - --xds-port=8001
-        - --envoy-service-http-port=80
-        - --envoy-service-https-port=443
-        - --contour-cafile=/ca/cacert.pem
-        - --contour-cert-file=/certs/tls.crt
-        - --contour-key-file=/certs/tls.key
-        - --config-path=/config/contour.yaml
-        command: ["contour"]
-        image: docker.io/projectcontour/contour:v1.3.0
-        imagePullPolicy: Always
-        name: contour
-        ports:
-        - containerPort: 8001
-          name: xds
-          protocol: TCP
-        - containerPort: 8000
-          name: debug
-          protocol: TCP
-        livenessProbe:
-          httpGet:
-            path: /healthz
-            port: 8000
-        readinessProbe:
-          tcpSocket:
-            port: 8001
-          initialDelaySeconds: 15
-          periodSeconds: 10
-        volumeMounts:
-          - name: contourcert
-            mountPath: /certs
-            readOnly: true
-          - name: cacert
-            mountPath: /ca
-            readOnly: true
-          - name: contour-config
-            mountPath: /config
-            readOnly: true
-        env:
-        - name: POD_NAME
-          valueFrom:
-            fieldRef:
-              apiVersion: v1
-              fieldPath: metadata.name
-      dnsPolicy: ClusterFirst
-      serviceAccountName: contour
-      securityContext:
-        runAsNonRoot: true
-        runAsUser: 65534
-        runAsGroup: 65534
-      volumes:
-        - name: contourcert
-          secret:
-            secretName: contourcert
-        - name: cacert
-          secret:
-            secretName: cacert
-        - name: contour-config
-          configMap:
-            name: contour
-            defaultMode: 0644
-            items:
-            - key: contour.yaml
-              path: contour.yaml
diff --git a/assets/components/contour/contour/03-envoy.yaml b/assets/components/contour/contour/03-envoy.yaml
deleted file mode 100644
index cd6e6427..00000000
--- a/assets/components/contour/contour/03-envoy.yaml
+++ /dev/null
@@ -1,160 +0,0 @@
----
-# XXX: Lokomotive specific change
-apiVersion: v1
-kind: ServiceAccount
-metadata:
-  name: envoy
-  namespace: projectcontour
----
-# XXX: Lokomotive specific change
-kind: RoleBinding
-apiVersion: rbac.authorization.k8s.io/v1
-metadata:
-  name: envoy-privileged-psp
-  namespace: projectcontour
-roleRef:
-  kind: ClusterRole
-  name: privileged-psp
-  apiGroup: rbac.authorization.k8s.io
-subjects:
-- kind: ServiceAccount
-  name: envoy
-  namespace: projectcontour
----
-apiVersion: apps/v1
-kind: DaemonSet
-metadata:
-  labels:
-    app: envoy
-  name: envoy
-  namespace: projectcontour
-spec:
-  updateStrategy:
-    type: RollingUpdate
-    rollingUpdate:
-      maxUnavailable: 10%
-  selector:
-    matchLabels:
-      app: envoy
-  template:
-    metadata:
-      annotations:
-        prometheus.io/scrape: "true"
-        prometheus.io/port: "8002"
-        prometheus.io/path: "/stats/prometheus"
-      labels:
-        app: envoy
-    spec:
-      containers:
-      - command:
-        - /bin/contour
-        args:
-          - envoy
-          - shutdown-manager
-        image: docker.io/projectcontour/contour:v1.3.0
-        imagePullPolicy: Always
-        lifecycle:
-          preStop:
-            httpGet:
-              path: /shutdown
-              port: 8090
-              scheme: HTTP
-        livenessProbe:
-          httpGet:
-            path: /healthz
-            port: 8090
-          initialDelaySeconds: 3
-          periodSeconds: 10
-        name: shutdown-manager
-      - args:
-        - -c
-        - /config/envoy.json
-        - --service-cluster $(CONTOUR_NAMESPACE)
-        - --service-node $(ENVOY_POD_NAME)
-        - --log-level info
-        command:
-        - envoy
-        image: docker.io/envoyproxy/envoy:v1.13.1
-        imagePullPolicy: IfNotPresent
-        name: envoy
-        env:
-        - name: CONTOUR_NAMESPACE
-          valueFrom:
-            fieldRef:
-              apiVersion: v1
-              fieldPath: metadata.namespace
-        - name: ENVOY_POD_NAME
-          valueFrom:
-            fieldRef:
-              apiVersion: v1
-              fieldPath: metadata.name
-        ports:
-        - containerPort: 80
-          hostPort: 80
-          name: http
-          protocol: TCP
-        - containerPort: 443
-          hostPort: 443
-          name: https
-          protocol: TCP
-        readinessProbe:
-          httpGet:
-            path: /ready
-            port: 8002
-          initialDelaySeconds: 3
-          periodSeconds: 4
-        volumeMounts:
-          - name: envoy-config
-            mountPath: /config
-          - name: envoycert
-            mountPath: /certs
-          - name: cacert
-            mountPath: /ca
-        lifecycle:
-          preStop:
-            httpGet:
-              path: /shutdown
-              port: 8090
-              scheme: HTTP
-      initContainers:
-      - args:
-        - bootstrap
-        - /config/envoy.json
-        - --xds-address=contour
-        - --xds-port=8001
-        - --envoy-cafile=/ca/cacert.pem
-        - --envoy-cert-file=/certs/tls.crt
-        - --envoy-key-file=/certs/tls.key
-        command:
-        - contour
-        image: docker.io/projectcontour/contour:v1.3.0
-        imagePullPolicy: Always
-        name: envoy-initconfig
-        volumeMounts:
-        - name: envoy-config
-          mountPath: /config
-        - name: envoycert
-          mountPath: /certs
-          readOnly: true
-        - name: cacert
-          mountPath: /ca
-          readOnly: true
-        env:
-        - name: CONTOUR_NAMESPACE
-          valueFrom:
-            fieldRef:
-              fieldPath: metadata.namespace
-      automountServiceAccountToken: false
-      # XXX: Lokomotive specific change
-      serviceAccountName: envoy
-      terminationGracePeriodSeconds: 300
-      volumes:
-        - name: envoy-config
-          emptyDir: {}
-        - name: envoycert
-          secret:
-            secretName: envoycert
-        - name: cacert
-          secret:
-            secretName: cacert
-      restartPolicy: Always
diff --git a/docs/configuration-reference/components/contour.md b/docs/configuration-reference/components/contour.md
index f4d18456..c536ba00 100644
--- a/docs/configuration-reference/components/contour.md
+++ b/docs/configuration-reference/components/contour.md
@@ -35,6 +35,28 @@ component "contour" {
   # Optional arguments
   service_monitor = false
   ingress_hosts = ["*.example.lokomotive.org"]
+
+  node_affinity {
+    key      = "node-role.kubernetes.io/node"
+    operator = "Exists"
+  }
+
+  node_affinity {
+    key      = "network.lokomotive.io"
+    operator = "In"
+
+    # If the `operator` is set to `"In"`, `values` should be specified.
+    values = [
+      "foo",
+    ]
+  }
+
+  toleration {
+    key      = "network.lokomotive.io"
+    operator = "Equal"
+    value    = "contour"
+    effect   = "NoSchedule"
+  }
 }
 ```
 
@@ -48,6 +70,8 @@ Example:
 |------------------|---------------------------------------------------------------------------------------------|:-------:|:--------:|
 | `service_monitor`| Create ServiceMonitor for Prometheus to scrape Contour and Envoy metrics.                   | false   | false    |
 | `ingress_hosts`  | [ExternalDNS component](external-dns.md) creates DNS entries from the values provided.      | ""      | false    |
+| `node_affinity`  | Node affinity for deploying the operator pod and envoy daemonset.                           | -       | false    |
+| `toleration`     | Tolerations that the operator and envoy pods will tolerate.                                 | -       | false    |
 
 ## Applying
 
diff --git a/docs/configuration-reference/components/rook-ceph.md b/docs/configuration-reference/components/rook-ceph.md
index 022c5a9e..185ba64c 100644
--- a/docs/configuration-reference/components/rook-ceph.md
+++ b/docs/configuration-reference/components/rook-ceph.md
@@ -30,11 +30,11 @@ component "rook-ceph" {
   namespace = "rook-test"
   monitor_count = 3
   metadata_device = "md127"
-  node_selector {
+  node_affinity {
     key      = "node-role.kubernetes.io/storage"
     operator = "Exists"
   }
-  node_selector {
+  node_affinity {
     key      = "storage.lokomotive.io"
     operator = "In"
 
@@ -66,7 +66,7 @@ Example:
 |---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|:-------:|:--------:|
 | `namespace`         | Namespace to deploy the Ceph cluster into. Must be the same as the rook operator.                                                                  | rook    | false    |
 | `monitor_count`     | Number of Ceph monitors to deploy. An odd number like 3 or 5 is recommended which should also be sufficient for most cases.                        | 1       | false    |
-| `node_selector`     | Node selectors for deploying the Ceph cluster pods.                                                                                                | -       | false    |
+| `node_affinity`     | Node affinity for deploying the Ceph cluster pods.                                                                                                 | -       | false    |
 | `toleration`        | Tolerations that the Ceph cluster pods will tolerate.                                                                                              | -       | false    |
 | `metadata_device`   | Name of the device to store the metadata on each storage machine. **Note**: Provide just the name of the device and skip prefixing with `/dev/`.   | -       | false    |
 
diff --git a/docs/configuration-reference/components/rook.md b/docs/configuration-reference/components/rook.md
index 9e454728..b1a8c465 100644
--- a/docs/configuration-reference/components/rook.md
+++ b/docs/configuration-reference/components/rook.md
@@ -30,12 +30,12 @@ component "rook" {
   # Optional arguments
   namespace = "rook-test"
 
-  node_selector {
+  node_affinity {
     key      = "node-role.kubernetes.io/node"
     operator = "Exists"
   }
 
-  node_selector {
+  node_affinity {
     key      = "storage.lokomotive.io"
     operator = "In"
 
@@ -69,7 +69,7 @@ Example:
 | Argument                     | Description                                                                                              | Default | Required |
 |------------------------------|----------------------------------------------------------------------------------------------------------|:-------:|:--------:|
 | `namespace`                  | Namespace to deploy the rook operator into.                                                              | rook    | false    |
-| `node_selector`              | Node selectors for deploying the operator pod.                                                           | -       | false    |
+| `node_affinity`              | Node affinity for deploying the operator pod.                                                            | -       | false    |
 | `toleration`                 | Tolerations that the operator's pods will tolerate.                                                      | -       | false    |
 | `agent_toleration_key`       | Toleration key for the rook agent pods.                                                                  | -       | false    |
 | `agent_toleration_effect`    | Toleration effect for the rook agent pods. Needs to be specified if `agent_toleration_key` is set.       | -       | false    |
diff --git a/pkg/assets/generated_assets.go b/pkg/assets/generated_assets.go
index 59429d8c..7049c9ad 100644
--- a/pkg/assets/generated_assets.go
+++ b/pkg/assets/generated_assets.go
@@ -489,20 +489,6 @@ var vfsgenAssets = func() http.FileSystem {
 
 			compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4c\x8e\x31\x0a\xc3\x30\x0c\x45\x77\x9f\x42\x17\x30\x24\x5b\xd1\x9a\xa9\x5b\xa0\xa5\xbb\x70\x44\x71\x9b\x58\x42\x56\x42\x7b\xfb\x12\xd3\x40\xb6\xff\x24\x3d\xf1\x63\x8c\x81\x34\x3f\xd8\x6a\x96\x82\xb0\xf5\xe1\x9d\xcb\x84\x70\x63\xdb\x72\xe2\xb0\xb0\xd3\x44\x4e\x18\x00\x0a\x2d\x8c\x90\xa4\xb8\xac\xf6\xe7\xaa\x94\x18\x41\x4d\x5e\x9c\xfc\xd8\x55\xe5\xb4\x1b\x2a\xe6\x75\x0f\xb1\x45\x84\x4b\xd7\xf5\x01\xe0\xf8\xf5\x99\x6a\x23\x35\x71\x49\x32\x23\xdc\x87\xb1\x4d\x9c\xec\xc9\x3e\x9e\xa5\xca\x33\x27\x17\xc3\x76\x40\xaa\xe7\x2e\xfe\x55\x46\x18\xe6\xb5\x3a\xdb\x75\x0c\xbf\x00\x00\x00\xff\xff\x20\xa2\xdc\x91\xd9\x00\x00\x00"),
 		},
-		"/components/contour/contour/03-contour.yaml": &vfsgen۰CompressedFileInfo{
-			name:             "03-contour.yaml",
-			modTime:          time.Date(1970, 1, 1, 0, 0, 1, 0, time.UTC),
-			uncompressedSize: 2698,
-
-			compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\x56\x4d\x6f\xe3\x36\x10\xbd\xfb\x57\x0c\x52\xf4\x28\x5b\xee\x26\x8b\x85\x80\x1c\x8c\x64\x77\x51\xb4\xc9\x1a\x71\xb6\x97\xa2\x28\x26\xe4\xd8\x62\x4d\x91\x02\x39\x72\xac\xfe\xfa\x82\x92\xe5\x88\x92\xbc\x41\x99\x4b\xc4\x37\xf3\x38\x7c\xf3\x41\x27\x49\x32\xc3\x52\xfd\x41\xce\x2b\x6b\x32\xc0\xb2\xf4\x8b\xc3\x72\xb6\x57\x46\x66\x70\x4f\xa5\xb6\x75\x41\x86\x67\x05\x31\x4a\x64\xcc\x66\x00\x1a\x5f\x48\xfb\xf0\x1f\x04\x87\x0c\x84\x35\x6c\x2b\x37\x03\x30\x58\xd0\xf0\xdb\x97\x28\x28\x83\xd2\xd9\x7f\x48\x70\x87\xf9\x92\x44\xa0\x70\x54\x6a\x25\xd0\x67\xf0\xcb\x0c\xc0\xb3\x43\xa6\x5d\xdd\x92\x73\x5d\x52\x06\x4f\x56\x6b\x65\x76\xdf\x4b\x89\x4c\xcd\xbe\xeb\xef\xb4\xa6\x00\x3f\xc1\x73\xae\x3c\x1c\x50\x57\x04\x76\x0b\x05\x1e\x37\x95\xdb\x11\x14\x84\xc6\x03\xe7\xc8\x20\x2b\xa7\xcc\x0e\xb0\x63\x80\xea\x8d\x34\x30\x70\x4e\x60\xe8\x15\x9e\xda\xa0\x36\xc4\xf0\xaa\xb4\x86\x17\x02\xe1\x08\x99\x24\x6c\x95\xf3\x3c\x3f\x79\x74\x67\x64\x70\x93\xfe\x1c\xc2\x27\x4d\x82\xad\x6b\x63\x2a\x90\x45\xfe\x7b\x4f\xac\x91\x5c\x4c\x45\xa9\xcf\x77\xe8\x6b\xdc\x18\x1b\x63\x19\x59\x59\x73\xf6\x87\x20\x63\x41\x9c\x53\xe5\xe7\xca\x2e\xbc\x70\x18\x34\xba\x62\x57\xd1\xd5\x05\xa3\xd2\x3a\xce\xe0\xea\x53\x9a\xa6\x9d\x89\x8e\xa2\x1a\xc5\x05\xd0\xa5\xa7\x01\xb7\x5b\x65\x14\xd7\xbd\x20\xac\x5c\x19\x56\xab\x11\x10\x8e\xa6\x2d\x39\x47\xf2\xbe\xd1\x7a\x23\x72\x92\x55\xd0\xfa\xd7\x9d\xb1\xe7\xed\xcf\x47\x12\x55\xb8\x59\xdf\x35\x69\x78\x4f\x9c\xcf\xe4\x8a\x3e\x78\x8e\x7a\x13\x69\xdc\x5f\x13\x7a\xf7\xd7\xe8\x8e\x6f\x8b\x6d\x69\xb5\xdd\xd5\xbf\x51\x9d\xc1\xbe\x7a\x21\x67\x88\xa9\x11\x2f\xb7\x9e\x43\x15\x47\x1e\xaf\xa4\x76\x39\x67\xb0\x4c\xd3\xd3\x7e\xe0\x45\x65\xc8\x9d\x8f\x4e\x00\xdd\xae\x17\x48\x02\x9e\xdc\x81\x7a\xdf\x49\xa2\x8c\xd0\x95\x67\x72\xd1\xee\x51\xfa\x04\xa5\x74\xe4\xfd\x6d\x3a\x6f\xfe\x46\x78\xc8\xe9\xed\xa7\x34\x5d\x46\x08\x99\x83\xad\x93\x70\x8e\x12\x94\xe4\xcc\x65\x67\xf8\x8e\xd9\x89\xf0\xfa\xfa\x43\x64\x78\x52\x2b\x11\xb8\x55\x9a\x6e\x17\x02\x17\x02\x05\x39\x9e\x97\x54\x4c\x5b\x92\xe3\xe4\x64\x4c\x8e\xfd\x82\xb5\x9f\x0b\xc7\x93\xc6\x7b\xaa\x47\xb6\x7b\xaa\x87\xb6\x5b\xb5\x4b\x4a\xe4\xfc\x76\xd1\x7e\x2c\x4e\xfe\xf3\x1a\x0b\x7d\x36\x16\xb6\x28\x30\x4c\xad\x3f\xaf\x4e\xf8\xd5\x5f\x67\x50\x15\x18\xba\x54\x5a\xb1\x27\xd7\xf4\x44\x34\x8d\x3a\xc6\xec\xb0\x9c\x7f\xe8\xa9\xdd\xb8\xad\x2b\xad\xd7\x56\x2b\x51\x67\xb0\xd2\xaf\x58\xfb\x33\x3e\x1c\x77\xed\x0a\x52\x46\x89\x3f\x17\xc7\xba\xe9\xc4\x28\x6d\x1d\xc9\x51\xfa\xa8\x8b\x2c\x5b\x61\x75\x06\xcf\x77\xeb\x1f\x32\xa5\x23\x26\x49\x2f\xd5\xee\x5d\x2e\xad\x0e\x64\xc8\xfb\xb5\xb3\x2f\xd4\x6f\x97\x50\x0e\x5f\x89\xe3\x0e\x0a\xf2\x67\xb0\xc8\x09\x35\xe7\xff\xc6\xd0\x38\x12\x47\x28\xd5\x24\x39\x8b\x72\x13\x72\x30\xa4\x9f\xd2\x25\x8c\x01\x85\xfa\x9e\x34\xd6\x1b\x12\xd6\x48\x9f\xc1\xf2\xa6\x7f\x33\x72\xca\xca\x37\xec\x2d\x82\x83\xd5\x55\x41\x0f\xb6\x32\xec\xe3\x11\x13\xa5\x2c\x94\x5d\x14\x48\x11\x3c\xd6\xed\x65\x9b\x9a\x8c\xd0\x70\xaf\x6f\x46\xd7\x19\x84\x71\x3b\x45\x8b\x3f\x66\xc4\xff\x49\xd7\x35\x55\x53\xf6\x97\x69\xc7\xf0\x05\x6a\x32\x87\x7e\x5d\xb6\xa7\xac\xbf\xdd\xff\xfd\xb8\x7a\xf8\xdc\x23\x68\x1e\xd1\x2f\xce\x0e\x26\xf0\x56\x91\x96\x4f\xb4\x1d\x4e\xd7\xfe\x2f\x88\xc3\x72\x00\x36\x4e\x6d\xa4\xdd\x03\x37\xef\x0d\x54\x69\x7c\xd7\x5c\x77\xed\x2c\xfc\x12\x5e\xd8\x13\x7a\x9a\x51\x2b\x21\xc2\x7d\x1f\x27\xda\xcd\x93\xa8\x9c\xe2\xfa\xce\x1a\xa6\x63\xaf\xae\x5c\x65\x56\xfe\xd1\x9a\x27\x6b\x79\x20\x43\x03\x7d\xf7\xe4\x32\xf8\x78\x73\xf3\xe1\x3a\x06\xbe\x3a\x5b\x95\x31\xd2\x96\x93\x1f\x6b\x37\x5d\x47\x9e\x84\x1b\x96\x78\xbb\xf7\x78\xd1\xeb\x62\x09\xbd\x4b\x86\xd3\x3c\x97\x6a\xa7\xdd\x78\xc0\x32\xa6\x9c\x1e\x65\x4d\x86\x68\x8b\x95\xe6\x07\x2b\x29\x83\xf4\xe3\xf5\x75\x84\x2a\xa6\x62\xf0\xdc\x26\xb0\x0f\xef\xe8\xe4\x94\x6e\x57\x3b\x4e\x22\x83\xff\x02\x00\x00\xff\xff\xe9\x50\xd4\xb2\x8a\x0a\x00\x00"),
-		},
-		"/components/contour/contour/03-envoy.yaml": &vfsgen۰CompressedFileInfo{
-			name:             "03-envoy.yaml",
-			modTime:          time.Date(1970, 1, 1, 0, 0, 1, 0, time.UTC),
-			uncompressedSize: 3954,
-
-			compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x57\x5f\x6f\xdb\xb6\x17\x7d\xf7\xa7\x20\xf2\xeb\x0f\xe8\x1e\x64\xd9\x4d\x1f\x32\x01\x79\xc8\x92\x2c\x1b\xd0\x3a\x46\x9c\x14\xd9\x53\x41\x53\xd7\x36\x6b\x8a\x97\x20\xaf\xd4\xa8\xc3\xbe\xfb\x40\xc9\x96\x45\xd9\x72\x5c\x0c\x1d\x96\x27\x87\xf7\xdf\xe1\x3d\xe7\x5e\xda\x51\x14\x0d\xfe\xc7\x9e\x9f\x9f\x13\xf6\x01\xd7\x98\x21\xc9\x02\x98\x33\x20\xe4\x42\x0a\x26\x56\x5c\x2f\x61\xc0\x8d\xfc\x04\xd6\x49\xd4\x09\x2b\xc6\x83\xb5\xd4\x69\xc2\x66\x60\x0b\x29\xe0\x4a\x08\xcc\x35\x0d\x32\x20\x9e\x72\xe2\xc9\x80\x31\xcd\x33\x48\x18\xe8\x02\xcb\xcd\x7f\xce\x70\x01\x09\x33\x16\xbf\x80\x20\x81\x9a\x30\xb7\x83\xd3\xaa\xd7\xe5\x1e\x50\xc1\x2f\x52\xa7\x52\x2f\x03\x3c\x76\xce\xc5\x90\xe7\xb4\x42\x2b\xbf\x71\x92\xa8\x87\xeb\x0b\x37\x94\x18\x17\xe3\x3e\x50\x91\xb1\xb2\x90\x0a\x96\x90\x46\xc6\x99\xa3\x18\x2d\x2a\x78\x80\x85\xcf\x50\x03\xb9\x56\xb9\x23\xb0\x1e\x4f\x93\x75\x2f\x1f\x37\xf2\xce\x62\x6e\x8e\xe0\x1b\xb8\x7c\xee\x0b\xb9\x64\x10\xb1\x83\x2d\xfd\xbe\x46\xb6\x9b\xc2\x8d\x71\x71\xc3\xd4\x0d\x87\x0c\xf5\x0c\x42\x92\x14\x9f\x83\x72\xfe\x93\x87\x6b\xc2\x32\xa7\x14\xf5\x3c\xf9\xf0\xdc\xa4\x9c\x60\x46\x96\x13\x2c\xcb\x3a\x21\x95\x06\x2a\xce\x94\xd4\xcb\xa7\xca\xa1\x3a\xb7\xed\x93\xda\x95\xb1\x8c\xbf\x3c\x69\x5e\x70\xa9\xf8\x5c\x41\xc2\xc6\xa3\xff\x0f\x18\x73\xa0\x40\x10\xda\xda\x2b\xe3\x24\x56\x1f\x5a\x90\x3b\xa0\x09\x32\xa3\x9a\x9c\xed\x7b\x56\xae\x5a\x23\x55\xcd\x6f\xa2\x99\xbf\x4e\x06\xb4\x82\xbc\x52\x8b\x13\x96\x7b\xcc\x67\x64\x73\x38\xeb\x71\x32\x68\x29\x61\x67\x17\xa3\xd1\xbb\x5e\x17\x4e\xab\x84\x9d\xc5\x8e\x38\xb9\x78\x67\xdb\xfa\xab\xe0\x0e\x9d\x5b\x30\xb6\x6d\xaa\xff\xf3\x8d\xe6\x52\x83\x6d\xdc\x23\x26\x30\xcb\xb8\x4e\x77\xf1\x11\x8b\xe7\x52\xc7\x5b\x52\x9a\xb4\x76\xd9\x2a\xe2\xdd\x76\x35\xb6\x27\x6e\x95\x53\x8a\x5f\x75\x94\x71\xcd\x97\xb0\x0b\x96\x19\x5f\x42\xc2\x52\x14\x6b\xb0\xd5\xa5\x02\xe6\xb7\xc5\x92\x62\x3c\x3c\x1f\x8e\xc2\xb0\x69\xae\xd4\x14\x95\x14\x65\xc2\xae\xd4\x57\x5e\xba\xc6\xae\xe4\x02\x44\x29\x14\xb4\x81\x19\x0b\x33\x42\xd3\x3e\x62\x6c\x45\x64\xee\x80\xc2\x43\xc6\xea\xde\xc6\x5b\xdc\x5d\x6b\x45\xce\xc5\xe8\xe7\x51\xc7\xe0\xc4\x0a\xbc\xa4\x7f\x7b\x7c\x9c\xb6\xc0\x14\xa0\xc1\xb9\xa9\xc5\x79\x00\xe8\x60\xed\x4d\xe5\x15\x70\x45\xab\x6f\x83\x57\xcb\x4a\x2d\x49\x72\x75\x03\x8a\x97\x33\x10\xa8\x53\x97\xb0\xf3\xf6\xb5\xc1\x4a\x4c\x1b\xd3\x78\x17\x5c\x8f\x5f\x0f\x37\x51\x87\xd8\x88\x45\xa2\x2d\x05\x81\x7a\x21\x97\x71\xc5\xf5\xf0\x8b\x43\xdd\xf6\x8c\x5c\xbd\x5f\x22\x51\xaf\x30\xf6\xe6\xed\xf5\xfd\xe4\xf1\xfe\xe9\xe1\xf3\xe4\xea\xe3\xed\x6c\x7a\x75\x7d\xfb\xd3\xc1\x00\x8d\x29\xb0\x37\x6f\x6f\x27\x9f\xee\xff\xf8\x3c\xbd\xbf\xa9\xfc\x43\x57\x85\xcb\x48\x41\x01\x8a\x49\xbd\xc0\xc6\x74\x40\xaf\xa1\x10\xf7\xb4\x56\x99\x8d\xc5\x97\xb2\xfe\xe8\x55\x36\x3e\x1f\x8e\xfb\x65\xf6\xfb\x62\x82\x34\xb5\xe0\xa0\x5a\x9a\xed\x36\x86\xb5\x40\x17\x6d\x20\xb5\xcb\x5e\x0b\x5a\x2c\x15\x5c\xe5\xf0\xab\xc5\x2c\x54\xc3\x42\x82\x4a\x37\x8f\x42\xfb\xaf\xf3\x4e\x86\xc6\x2a\x68\x5a\xe9\x68\xbb\x9e\x86\xcd\x72\xdd\x43\x15\xb6\xfa\x5f\x86\xb4\x5b\x6d\x68\x29\x50\x5b\xb3\x92\xa6\x1b\xd5\xb7\x07\x07\x1d\x1d\x38\xae\xef\xe3\xa7\x2a\x98\x7a\x24\x14\xa8\x12\xf6\x78\x3d\xed\xcf\xff\xfe\xfd\xf9\xc1\x02\xe1\xf9\xae\x82\x7b\xb5\x84\x05\x9e\xca\xef\x9e\x7b\x1f\x55\x1e\x9c\xfa\xd1\xbb\x7f\x30\xf5\xef\x1b\x53\x81\x2a\xcf\xe0\xa3\x7f\xf6\x3b\x6b\xbb\xfd\xb5\xa5\x9e\xee\x00\x47\xe6\x43\x6a\x0a\xe3\x3d\x73\x10\x2d\xc0\x52\x7f\x28\x58\x72\x07\x22\x05\x3f\x1e\xc6\xff\x8b\xcb\xdd\xd3\x70\x7d\xe0\xed\xec\xae\xce\x39\x22\x39\xb2\xdc\x9c\xbe\x41\x5f\x52\x17\xf1\x34\xb5\xe0\xdc\x65\xf7\xc9\xdd\xda\x3d\xd4\xcb\x8b\xd1\x68\x1c\x58\x36\x0c\xf2\x85\x54\x70\x19\x0b\x1e\xd7\xad\x1d\x1a\xc8\x0e\xf9\x81\xa5\x68\xe3\xea\xa9\x89\x49\xb9\xa1\x68\x31\xb1\x73\x5d\x43\xb9\xe7\xb9\x86\xf2\xd8\x06\xee\x22\xff\x41\xef\x7d\x5b\xba\x9e\x93\x8e\x3e\x0f\x6b\xfe\x15\xc5\x1f\xd1\xfb\x31\xb5\x1f\xd3\xba\x1f\xee\x7b\xad\xca\x84\xf9\xaf\x7e\x7b\xe9\xf6\x46\xa0\x67\x00\x7a\x13\xfd\xe8\x57\xe7\x94\x87\x85\xe7\x84\x15\xee\xf0\x27\xc6\x23\xae\x41\x27\x6c\xc1\x95\xdb\x7a\xbe\xfe\x7b\xac\xf6\x73\x41\xa2\xc9\xde\x6b\x4b\x60\x33\xa9\xab\xef\xdc\x77\x96\x0b\x98\x86\xab\xef\x7c\xb4\xd5\x51\x2d\x83\xd3\x15\x00\x99\xa1\xf2\x46\xda\x84\xfd\xf9\xd7\x49\xdc\x3b\x10\xb6\xbb\x6e\xea\xb3\x49\x4f\x4c\x2f\xf5\xaf\xa5\x0a\x02\x2c\x38\xe2\x96\x3a\xc3\xf1\x77\x00\x00\x00\xff\xff\x34\x99\x3a\x91\x72\x0f\x00\x00"),
-		},
 		"/components/contour/contour/README.md": &vfsgen۰CompressedFileInfo{
 			name:             "README.md",
 			modTime:          time.Date(1970, 1, 1, 0, 0, 1, 0, time.UTC),
@@ -4710,8 +4696,6 @@ var vfsgenAssets = func() http.FileSystem {
 		fs["/components/contour/contour/02-job-certgen.yaml"].(os.FileInfo),
 		fs["/components/contour/contour/02-rbac.yaml"].(os.FileInfo),
 		fs["/components/contour/contour/02-service-contour.yaml"].(os.FileInfo),
-		fs["/components/contour/contour/03-contour.yaml"].(os.FileInfo),
-		fs["/components/contour/contour/03-envoy.yaml"].(os.FileInfo),
 		fs["/components/contour/contour/README.md"].(os.FileInfo),
 	}
 	fs["/components/contour/manifests-metrics"].(*vfsgen۰DirInfo).entries = []os.FileInfo{
diff --git a/pkg/components/contour/component.go b/pkg/components/contour/component.go
index e1d49730..a070d932 100644
--- a/pkg/components/contour/component.go
+++ b/pkg/components/contour/component.go
@@ -49,6 +49,10 @@ type component struct {
 
 	// IngressHostsRaw is not accessible to the user
 	IngressHostsRaw string
+
+	NodeAffinity   []util.NodeAffinity `hcl:"node_affinity,block"`
+	Tolerations    []util.Toleration   `hcl:"toleration,block"`
+	TolerationsRaw string
 }
 
 func newComponent() *component {
@@ -82,14 +86,26 @@ func (c *component) RenderManifests() (map[string]string, error) {
 			return nil, errors.Wrap(err, "failed to walk assets")
 		}
 	}
+
 	// To store the comma separated string representation of IngressHosts
 	c.IngressHostsRaw = strings.Join(c.IngressHosts, ",")
-	// Parse envoy service template.
-	envoyServiceStr, err := util.RenderTemplate(envoyServiceTmpl, c)
+
+	// Generate YAML for the Rook operator deployment.
+	var err error
+	c.TolerationsRaw, err = util.RenderTolerations(c.Tolerations)
 	if err != nil {
-		return nil, errors.Wrap(err, "render template failed")
+		return nil, fmt.Errorf("failed to marshal operator tolerations: %w", err)
+	}
+
+	// Parse template with values
+	for k, v := range template {
+		rendered, err := util.RenderTemplate(v, c)
+		if err != nil {
+			return nil, fmt.Errorf("template rendering failed for %q: %w", k, err)
+		}
+
+		ret[k] = rendered
 	}
-	ret["02-service-envoy.yaml"] = envoyServiceStr
 
 	return ret, nil
 }
diff --git a/pkg/components/contour/template.go b/pkg/components/contour/template.go
index 2ab52987..3f91e4bf 100644
--- a/pkg/components/contour/template.go
+++ b/pkg/components/contour/template.go
@@ -14,7 +14,8 @@
 
 package contour
 
-const envoyServiceTmpl = `
+var template = map[string]string{
+	"02-service-envoy.yaml": `
 ---
 apiVersion: v1
 kind: Service
@@ -44,4 +45,315 @@ spec:
   selector:
     app: envoy
   type: LoadBalancer
-`
+`,
+
+	"03-contour.yaml": `
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  labels:
+    app: contour
+  name: contour
+  namespace: projectcontour
+spec:
+  replicas: 2
+  strategy:
+    type: RollingUpdate
+    rollingUpdate:
+      # This value of maxSurge means that during a rolling update
+      # the new ReplicaSet will be created first.
+      maxSurge: 50%
+  selector:
+    matchLabels:
+      app: contour
+  template:
+    metadata:
+      annotations:
+        prometheus.io/scrape: "true"
+        prometheus.io/port: "8000"
+      labels:
+        app: contour
+    spec:
+      affinity:
+        podAntiAffinity:
+          preferredDuringSchedulingIgnoredDuringExecution:
+          - podAffinityTerm:
+              labelSelector:
+                matchLabels:
+                  app: contour
+              topologyKey: kubernetes.io/hostname
+            weight: 100
+        {{- if .NodeAffinity }}
+        nodeAffinity:
+          requiredDuringSchedulingIgnoredDuringExecution:
+            nodeSelectorTerms:
+              - matchExpressions:
+                {{- range $item := .NodeAffinity }}
+                - key: {{ $item.Key }}
+                  operator: {{ $item.Operator }}
+                  {{- if $item.Values }}
+                  values:
+                    {{- range $val := $item.Values }}
+                    - {{ $val }}
+                    {{- end }}
+                  {{- end }}
+                {{- end }}
+        {{- end}}
+      {{- if .TolerationsRaw }}
+      tolerations: {{ .TolerationsRaw }}
+      {{- end }}
+      containers:
+      - args:
+        - serve
+        - --incluster
+        - --xds-address=0.0.0.0
+        - --xds-port=8001
+        - --envoy-service-http-port=80
+        - --envoy-service-https-port=443
+        - --contour-cafile=/ca/cacert.pem
+        - --contour-cert-file=/certs/tls.crt
+        - --contour-key-file=/certs/tls.key
+        - --config-path=/config/contour.yaml
+        command: ["contour"]
+        image: docker.io/projectcontour/contour:v1.3.0
+        imagePullPolicy: Always
+        name: contour
+        ports:
+        - containerPort: 8001
+          name: xds
+          protocol: TCP
+        - containerPort: 8000
+          name: debug
+          protocol: TCP
+        livenessProbe:
+          httpGet:
+            path: /healthz
+            port: 8000
+        readinessProbe:
+          tcpSocket:
+            port: 8001
+          initialDelaySeconds: 15
+          periodSeconds: 10
+        volumeMounts:
+          - name: contourcert
+            mountPath: /certs
+            readOnly: true
+          - name: cacert
+            mountPath: /ca
+            readOnly: true
+          - name: contour-config
+            mountPath: /config
+            readOnly: true
+        env:
+        - name: POD_NAME
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.name
+      dnsPolicy: ClusterFirst
+      serviceAccountName: contour
+      securityContext:
+        runAsNonRoot: true
+        runAsUser: 65534
+        runAsGroup: 65534
+      volumes:
+        - name: contourcert
+          secret:
+            secretName: contourcert
+        - name: cacert
+          secret:
+            secretName: cacert
+        - name: contour-config
+          configMap:
+            name: contour
+            defaultMode: 0644
+            items:
+            - key: contour.yaml
+              path: contour.yaml
+`,
+
+	"03-envoy.yaml": `
+---
+# XXX: Lokomotive specific change
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: envoy
+  namespace: projectcontour
+---
+# XXX: Lokomotive specific change
+kind: RoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: envoy-privileged-psp
+  namespace: projectcontour
+roleRef:
+  kind: ClusterRole
+  name: privileged-psp
+  apiGroup: rbac.authorization.k8s.io
+subjects:
+- kind: ServiceAccount
+  name: envoy
+  namespace: projectcontour
+---
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+  labels:
+    app: envoy
+  name: envoy
+  namespace: projectcontour
+spec:
+  updateStrategy:
+    type: RollingUpdate
+    rollingUpdate:
+      maxUnavailable: 10%
+  selector:
+    matchLabels:
+      app: envoy
+  template:
+    metadata:
+      annotations:
+        prometheus.io/scrape: "true"
+        prometheus.io/port: "8002"
+        prometheus.io/path: "/stats/prometheus"
+      labels:
+        app: envoy
+    spec:
+      {{- if .NodeAffinity }}
+      affinity:
+        nodeAffinity:
+          requiredDuringSchedulingIgnoredDuringExecution:
+            nodeSelectorTerms:
+              - matchExpressions:
+                {{- range $item := .NodeAffinity }}
+                - key: {{ $item.Key }}
+                  operator: {{ $item.Operator }}
+                  {{- if $item.Values }}
+                  values:
+                    {{- range $val := $item.Values }}
+                    - {{ $val }}
+                    {{- end }}
+                  {{- end }}
+                {{- end }}
+      {{- end}}
+      {{- if .TolerationsRaw }}
+      tolerations: {{ .TolerationsRaw }}
+      {{- end }}
+      containers:
+      - command:
+        - /bin/contour
+        args:
+          - envoy
+          - shutdown-manager
+        image: docker.io/projectcontour/contour:v1.3.0
+        imagePullPolicy: Always
+        lifecycle:
+          preStop:
+            httpGet:
+              path: /shutdown
+              port: 8090
+              scheme: HTTP
+        livenessProbe:
+          httpGet:
+            path: /healthz
+            port: 8090
+          initialDelaySeconds: 3
+          periodSeconds: 10
+        name: shutdown-manager
+      - args:
+        - -c
+        - /config/envoy.json
+        - --service-cluster $(CONTOUR_NAMESPACE)
+        - --service-node $(ENVOY_POD_NAME)
+        - --log-level info
+        command:
+        - envoy
+        image: docker.io/envoyproxy/envoy:v1.13.1
+        imagePullPolicy: IfNotPresent
+        name: envoy
+        env:
+        - name: CONTOUR_NAMESPACE
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.namespace
+        - name: ENVOY_POD_NAME
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.name
+        ports:
+        - containerPort: 80
+          hostPort: 80
+          name: http
+          protocol: TCP
+        - containerPort: 443
+          hostPort: 443
+          name: https
+          protocol: TCP
+        readinessProbe:
+          httpGet:
+            path: /ready
+            port: 8002
+          initialDelaySeconds: 3
+          periodSeconds: 4
+        volumeMounts:
+          - name: envoy-config
+            mountPath: /config
+          - name: envoycert
+            mountPath: /certs
+          - name: cacert
+            mountPath: /ca
+        lifecycle:
+          preStop:
+            httpGet:
+              path: /shutdown
+              port: 8090
+              scheme: HTTP
+      initContainers:
+      - args:
+        - bootstrap
+        - /config/envoy.json
+        - --xds-address=contour
+        - --xds-port=8001
+        - --envoy-cafile=/ca/cacert.pem
+        - --envoy-cert-file=/certs/tls.crt
+        - --envoy-key-file=/certs/tls.key
+        command:
+        - contour
+        image: docker.io/projectcontour/contour:v1.3.0
+        imagePullPolicy: Always
+        name: envoy-initconfig
+        volumeMounts:
+        - name: envoy-config
+          mountPath: /config
+        - name: envoycert
+          mountPath: /certs
+          readOnly: true
+        - name: cacert
+          mountPath: /ca
+          readOnly: true
+        env:
+        - name: CONTOUR_NAMESPACE
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.namespace
+      automountServiceAccountToken: false
+      # XXX: Lokomotive specific change
+      serviceAccountName: envoy
+      terminationGracePeriodSeconds: 300
+      volumes:
+        - name: envoy-config
+          emptyDir: {}
+        - name: envoycert
+          secret:
+            secretName: envoycert
+        - name: cacert
+          secret:
+            secretName: cacert
+      restartPolicy: Always
+
+`,
+}
diff --git a/pkg/components/rook-ceph/component.go b/pkg/components/rook-ceph/component.go
index 974d983d..5d3f822d 100644
--- a/pkg/components/rook-ceph/component.go
+++ b/pkg/components/rook-ceph/component.go
@@ -31,7 +31,7 @@ func init() {
 type component struct {
 	Namespace      string              `hcl:"namespace,optional"`
 	MonitorCount   int                 `hcl:"monitor_count,optional"`
-	NodeSelectors  []util.NodeSelector `hcl:"node_selector,block"`
+	NodeAffinity   []util.NodeAffinity `hcl:"node_affinity,block"`
 	MetadataDevice string              `hcl:"metadata_device,optional"`
 	Tolerations    []util.Toleration   `hcl:"toleration,block"`
 	TolerationsRaw string
diff --git a/pkg/components/rook-ceph/manifests.go b/pkg/components/rook-ceph/manifests.go
index cd81a9bc..979638b1 100644
--- a/pkg/components/rook-ceph/manifests.go
+++ b/pkg/components/rook-ceph/manifests.go
@@ -47,12 +47,12 @@ spec:
     deleteDataDirOnHosts: ""
   placement:
     all:
-      {{- if .NodeSelectors }}
+      {{- if .NodeAffinity }}
       nodeAffinity:
         requiredDuringSchedulingIgnoredDuringExecution:
           nodeSelectorTerms:
             - matchExpressions:
-              {{- range $item := .NodeSelectors }}
+              {{- range $item := .NodeAffinity }}
               - key: {{ $item.Key }}
                 operator: {{ $item.Operator }}
                 {{- if $item.Values }}
diff --git a/pkg/components/rook/component.go b/pkg/components/rook/component.go
index e7f1b857..f1cc87e5 100644
--- a/pkg/components/rook/component.go
+++ b/pkg/components/rook/component.go
@@ -30,7 +30,7 @@ func init() {
 
 type component struct {
 	Namespace                string              `hcl:"namespace,optional"`
-	NodeSelectors            []util.NodeSelector `hcl:"node_selector,block"`
+	NodeAffinity             []util.NodeAffinity `hcl:"node_affinity,block"`
 	Tolerations              []util.Toleration   `hcl:"toleration,block"`
 	TolerationsRaw           string
 	AgentTolerationKey       string `hcl:"agent_toleration_key,optional"`
diff --git a/pkg/components/rook/manifests.go b/pkg/components/rook/manifests.go
index e13c8680..bfcea2b4 100644
--- a/pkg/components/rook/manifests.go
+++ b/pkg/components/rook/manifests.go
@@ -1852,13 +1852,13 @@ spec:
       labels:
         app: rook-ceph-operator
     spec:
-      {{- if .NodeSelectors }}
+      {{- if .NodeAffinity }}
       affinity:
         nodeAffinity:
           requiredDuringSchedulingIgnoredDuringExecution:
             nodeSelectorTerms:
               - matchExpressions:
-                {{- range $item := .NodeSelectors }}
+                {{- range $item := .NodeAffinity }}
                 - key: {{ $item.Key }}
                   operator: {{ $item.Operator }}
                   {{- if $item.Values }}
diff --git a/pkg/components/util/types.go b/pkg/components/util/types.go
index 92e97475..8ebbd0a1 100644
--- a/pkg/components/util/types.go
+++ b/pkg/components/util/types.go
@@ -16,7 +16,9 @@ package util
 
 import "encoding/json"
 
-type NodeSelector struct {
+// NodeAffinity is a struct that other components can use to define the HCL format of NodeAffinity
+// in Kubernetes PodSpec.
+type NodeAffinity struct {
 	Key      string   `hcl:"key",json:"key,omitempty"`
 	Operator string   `hcl:"operator",json:"operator,omitempty"`
 	Values   []string `hcl:"values,optional",json:"values,omitempty"`


Test patch:
diff --git a/pkg/components/rook-ceph/component_test.go b/pkg/components/rook-ceph/component_test.go
index 81381ab4..0a8bb938 100644
--- a/pkg/components/rook-ceph/component_test.go
+++ b/pkg/components/rook-ceph/component_test.go
@@ -39,12 +39,12 @@ component "rook-ceph" {
 
   monitor_count = 3
 
-  node_selector {
+  node_affinity {
     key      = "node-role.kubernetes.io/storage"
     operator = "Exists"
   }
 
-  node_selector {
+  node_affinity {
     key      = "storage.lokomotive.io"
     operator = "In"