<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>terraform enterprise on Digi Hunch</title><link>https://static.digihunch.com/tag/terraform-enterprise/</link><description>Recent content in terraform enterprise on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Wed, 02 Apr 2025 12:51:03 -0400</lastBuildDate><atom:link href="https://static.digihunch.com/tag/terraform-enterprise/index.xml" rel="self" type="application/rss+xml"/><item><title>AKS Lessons Learned 2 of 2</title><link>https://static.digihunch.com/2021/12/aks-lessons-learned-2-of-2/</link><pubDate>Sat, 18 Dec 2021 01:18:00 -0400</pubDate><guid>https://static.digihunch.com/2021/12/aks-lessons-learned-2-of-2/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-aks-lession-2.webp" alt="Featured image of post AKS Lessons Learned 2 of 2" /&gt;&lt;p class="wp-block-paragraph"&gt;Even though Azure Kubernetes Service (AKS) is a managed service, building a cluster is not trivial. For help resources, I would start with the webinar &amp;#8220;Configure Your AKS cluster with Confidence&amp;#8221; from April 2021, which focuses on a set of working best practices (convention over configuration) but obviously not every recommendation suits every use case. For a deeper technical tour, the &lt;a href="https://www.youtube.com/channel/UCpIn7ox7j7bH_OFj7tYouOQ"&gt;John Savill&amp;#8217;s Technical Training channel&lt;/a&gt; has good videos (from 2020) on AKS &lt;a href="https://www.youtube.com/watch?v=c4nTKMU6fBU"&gt;overview&lt;/a&gt;, &lt;a href="https://www.youtube.com/watch?v=gksFrKiZjMc"&gt;high availability&lt;/a&gt; and &lt;a href="https://www.youtube.com/watch?v=6TZsd4toIbg"&gt;networking&lt;/a&gt;. Lastly, there is also an &lt;a href="https://www.the-aks-checklist.com/"&gt;AKS checklist&lt;/a&gt; to remind you of the implementation details to consider.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;All the references aside, I need to write down some gotchas from my implementation experience in the last two month.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="identity-and-access-management"&gt;Identity and Access Management&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;AKS is a special type of Azure resource in the sense that it manages other Azure services on user&amp;#8217;s behalf. Therefore the access management needs to consider several aspects:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-very-light-gray-to-cyan-bluish-gray-gradient-background has-background"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Access Type&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Mechanisms involved&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;User access Kubernetes API&lt;/td&gt;&lt;td&gt;Azure AD, Azure RBAC and Kubernetes RBAC. &lt;br&gt;&amp;#8211; Azure AD is for authentication&lt;br&gt;&amp;#8211; Azure RBAC for Kubernetes&lt;br&gt;&amp;#8211; Kubernetes RBAC&lt;/td&gt;&lt;td&gt;A user connects to Kube-API server using kubectl&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AKS access other Azure resource&lt;/td&gt;&lt;td&gt;There are several identities that represents different components of AKS. For example, the AKS cluster, the node agent pool, and each add-on.&lt;br&gt;The AKS cluster can be represented as a service principal, or managed identity (system assigned or user assigned). &lt;br&gt;The node agent pool can be represented as a managed identity&lt;/td&gt;&lt;td&gt;AKS cluster connects to a VNet in a different resource group. (requiring cluster&amp;#8217;s identity to have network contributor role on the target network resource group)&lt;br&gt;AKS node agent pulls images from ACR (requiring the node agent pool&amp;#8217;s identity to have ArcPull role on the target ACR)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Pod access other Azure resource&lt;/td&gt;&lt;td&gt;AAD-Pod Managed Identity&lt;/td&gt;&lt;td&gt;Business workload connects to managed database service such as PostgreSQL on Azure.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Pod access Kubernetes API&lt;/td&gt;&lt;td&gt;Access Kubernetes API using Service Account. This issue is solved completely by Kubernetes native mechanisms. Roles and ClusterRoles defines permissions. RoleBindings and ClusterRoleBindings associates Service Accounts with permissions&lt;/td&gt;&lt;td&gt;Workload access ConfigMap, Secret etc.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the first access type, for RBAC with user to access Kubernetes API, there is an overlap between Azure RBAC and Kubnernetes RBAC. Azure RBAC has four &lt;a href="https://docs.microsoft.com/en-us/azure/aks/manage-azure-rbac#create-role-assignments-for-users-to-access-cluster"&gt;built-in roles&lt;/a&gt; and three of them (reader, writer, admin) are namespaced. When you use Azure CLI to manage to assign one of those roles, the rolebinding and cluster rolebinding record stored in etcd will be updated accordingly. &lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-stripes"&gt;&lt;table class="has-black-color has-cyan-bluish-gray-background-color has-text-color has-background"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;RBAC mechansim&lt;/td&gt;&lt;td&gt;Use case&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Azure RBAC for Kubernetes&lt;/td&gt;&lt;td&gt;Manage RBAC programmatically using Azure CLI, or infrastructure as code&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Kubernetes RBAC&lt;/td&gt;&lt;td&gt;Manage RBAC declaratively with more granularity for all types of Kubernetes resources including CRD&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For ease of operation it is advised to use Kubernetes RBAC whenever possible. Azure RBAC is still used for RBAC at the level of Azure resource but not at the level of Kubernetes resource.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the the second access type, AKS cluster may use managed identity or service principal. Azure&amp;#8217;s recommendation is managed identity over service principal. Managed Identity is a wrapper around Service Principal with less overhead. Managed Identity can be system assigned (created at the time of cluster creation), or user assigned (can be created ahead of time by Azure administrator and imported to the cluster&amp;#8217;s context).&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The second access type can be further broken down because there are several components in AKS that uses their own identities. I list the &lt;a href="https://docs.microsoft.com/en-us/azure/aks/use-managed-identity"&gt;related managed identities&lt;/a&gt; as below:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-very-light-gray-to-cyan-bluish-gray-gradient-background has-background"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Name&lt;/td&gt;&lt;td&gt;Purpose&lt;/td&gt;&lt;td&gt;BYO identity with Terraform&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;cluster identity&lt;/td&gt;&lt;td&gt;This identity represents the cluster&lt;/td&gt;&lt;td&gt;Specify in &lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster#identity"&gt;identity&lt;/a&gt; block in kubernetes_cluster resource&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;agent pool identity&lt;/td&gt;&lt;td&gt;This identity represents kubelet running in the agent pool&lt;/td&gt;&lt;td&gt;Specify in &lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster#kubelet_identity"&gt;kubelet_identity&lt;/a&gt; block. &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;addon: azurepolicy&lt;/td&gt;&lt;td&gt;This identity represents azure policy addon to access the policy&lt;/td&gt;&lt;td&gt;N/A&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;addon: omsagent&lt;/td&gt;&lt;td&gt;This identity represents OMS agent to access monitoring etc&lt;/td&gt;&lt;td&gt;Specify in &lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster#oms_agent_identity"&gt;oms_agent_identity&lt;/a&gt; block &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;addon: secret&lt;/td&gt;&lt;td&gt;This identity represents to the secret addon, to access AKV&lt;/td&gt;&lt;td&gt;Specify in &lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster#secret_identity"&gt;secret_identity&lt;/a&gt; block &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;addon: ingress gateway &lt;/td&gt;&lt;td&gt;This identity represents the ingress application gateway&lt;/td&gt;&lt;td&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster#ingress_application_gateway_identity"&gt;ingress_application_gateway_identity&lt;/a&gt; block&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;By default, the system creates a new managed identity for each of the required identity above. For simplicity with identity management, we may create a managed identity and use it for all the occasions where an identity is needed and user assigned (BYO) identity is supported.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the &amp;#8220;az aks show&amp;#8221; command return (a JSON document), the &lt;em&gt;identity&lt;/em&gt; section (root level) reports the cluster identity, the &lt;em&gt;identityProfile&lt;/em&gt; section (root level) reports the agent pool (kubelet) identity. Other identities such as omsagent, are reported in their own child document.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="node-networking"&gt;Node Networking &lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Azure, a subnet can span across multiple availability zones. Therefore an AKS cluster can put its nodes on a single subnet with nodes evenly distributed across three AZs for high availability. The AZ of each node is indicated in the node label, and can be &lt;a href="https://docs.microsoft.com/en-us/azure/aks/availability-zones#verify-node-distribution-across-zones"&gt;displayed&lt;/a&gt; with kubectl command.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Within a single AZ, a good practice to minimize latency between nodes is to place the nodes in a proximity placement group (PPG). However, only a &lt;a href="https://docs.microsoft.com/en-us/azure/aks/reduce-latency-ppg#node-pools-and-proximity-placement-groups"&gt;single PPG&lt;/a&gt; can be associated with a node group. You can&amp;#8217;t have three PPGs, one in each AZ, for a single subnet. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="pod-networking"&gt;Pod Networking&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The default Pod networking model is kubenet, which involves overlay network. Pod-to-Pod traffic across nodes requires Network Address Translation (NAT). To overcome this performance tax, Azure introduces Azure CNI which gives each Pod an routable IP address from the VNet&amp;#8217;s CIDR. This requires advanced IP planning to prevent IP exhaustion. A risk introduced in Azure CNI is that all Pods are exposed on the V-net, which needs to be protected by Network Security Group and/or outbound firewall.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="dns"&gt;DNS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On the DNS side, when AKS cluster integrate with an external node network, it may create weird issues that are hard to troubleshoot. Another example is with DNS. If the V-Net uses an external DNS server (which is common for enterprises with hybrid network to use an on-premise DNS server), then the cluster creation failed with time-out with misleading error messages (for example, this&amp;nbsp;&lt;a href="https://github.com/Azure/AKS/issues/1972#issuecomment-868478020"&gt;comment&lt;/a&gt;). This is because the DNS name of the newly created cluster is not resolvable within the V-NET, which points to the on-prem DNS server. The fix to that is:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Use a BYO DNS zone (in Azure) for AKS cluster creation;&lt;/li&gt;&#10;&lt;li&gt;The AKS cluster will publish the A-record to the zone. To allow this to happen, the AKS cluster’s managed identity needs to have DNS contributor permission for the zone;&lt;/li&gt;&#10;&lt;li&gt;Configure the on-prem DNS for conditional forwarding to the DNS zone&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This fix will allow AKS to resolve its name and therefore confirm its own creation. Here is a good blog about the DNS zone&amp;nbsp;&lt;a href="https://blog.baeke.info/2021/07/01/dns-options-for-private-azure-kubernetes-service/"&gt;option&lt;/a&gt;s.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another potential issue introduced with the use of on-prem DNS server, is the resolution of single-label hostname of the nodes. This is not just an issue in the context of AKS. It is a generic issue with VMs running on a V-Net pointing to on-prem DNS, as explained in detail&amp;nbsp;&lt;a href="https://docs.microsoft.com/en-us/azure/virtual-network/virtual-networks-name-resolution-for-vms-and-role-instances#name-resolution-that-uses-your-own-dns-server"&gt;here&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this situation, we should use the fully qualified hostname instead of single-label hostname. The fully qualified hostname with DNS suffix can help the on-prem server to configure conditional forwarding. For example, when the DNS suffix is *.internal.cloudapp.net, then forward it to Azure’s virtual internal DNS server 168.63.129.16 which can resolve the hostname.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If only the Pods need to resolve those FQDNs, then we can configure Core-DNS with&amp;nbsp;&lt;a href="https://docs.microsoft.com/en-us/azure/aks/coredns-custom"&gt;conditional forwarding&lt;/a&gt;, which will take effect only at the cluster level without the need for changing the on-prem DNS. The Core-DNS configuration looks like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ConfigMap&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;coredns&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;custom&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;is&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;of&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;configmap&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;you&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;can&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;overwrite&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;your&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;changes&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;kube&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;system&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;data&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cloudapp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;override&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;you&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;may&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;select&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;any&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;here&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;but&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;it&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;must&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;end&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; .&lt;span style="color:#a6e22e"&gt;override&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;file&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;extension&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;rewrite&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;continue&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;regex&lt;/span&gt; &lt;span style="color:#f92672"&gt;^&lt;/span&gt;(.&lt;span style="color:#f92672"&gt;*&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;]{&lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;}&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;vmss&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;]{&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;})&lt;span style="color:#a6e22e"&gt;$&lt;/span&gt; {&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;}.&lt;span style="color:#a6e22e"&gt;internal&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;cloudapp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;net&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;answer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;^&lt;/span&gt;(.&lt;span style="color:#f92672"&gt;*&lt;/span&gt;)&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;internal&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;cloudapp&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;net$&lt;/span&gt; {&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;forward&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;internal&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;cloudapp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;net&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;168.63&lt;/span&gt;.&lt;span style="color:#ae81ff"&gt;129.16&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cloudapp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;server&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;internal&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;cloudapp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;net&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;53&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;errors&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cache&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;forward&lt;/span&gt; . &lt;span style="color:#ae81ff"&gt;168.63&lt;/span&gt;.&lt;span style="color:#ae81ff"&gt;129.16&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Alternatively, use Pod&amp;nbsp;&lt;a href="https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy"&gt;DNS policy&lt;/a&gt;&amp;nbsp;so that the Pod can use an external DNS server.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="initial-service-account"&gt;Initial Service Account&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When a cluster is created, an Azure AD user or group can be assigned as cluster administrator. For a CI/CD pipeline to interact with the newly created cluster, a service account in Kubernetes is needed. Suppose we use &lt;a href="https://static.digihunch.com/2021/11/infrastructure-deployment-in-terraform-2-2/"&gt;Terraform&lt;/a&gt; to create the AKS cluster, we can create such service account automatically with the Kubernetes provider. This requires that the Terraform execution environment to have network access to the cluster. If the AKS cluster is located in a private network, then the agent where Terraform CLI runs should also be on the network. Alternatively, use Terraform Enterprise hosted in an environment with access to the cluster&amp;#8217;s network.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="integration-with-azure-keyvault"&gt;Integration with Azure KeyVault&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Azure Key Vault can store several types of secrets, key value pair, X509 keys and certificate. When AKV is &lt;a href="https://docs.microsoft.com/en-us/azure/aks/csi-secrets-store-identity-access"&gt;integrated&lt;/a&gt; with an AKS cluster, the Kubernetes workload can access the secrets as mounted volumes, using CRD named SecretProviderClass. Further, they can be presented as Kubernetes Secret, using a Pod to &lt;a href="https://docs.microsoft.com/en-us/azure/aks/csi-secrets-store-driver#sync-mounted-content-with-a-kubernetes-secret"&gt;sync&lt;/a&gt; between mounted content and Secret. AKV has three types of entries: key, certificate and secret (key-value). The certificate entry requires both key and certificate are stored, with optional certificate chain. In my opinion this is an over design. Unless we need Azure to manage the certificate (e.g. rotation) I would simply use secret to store my own X509 key and certificate.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2021/12/aks-troubleshooting-lessons-learned/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;AKS Lessons Learned 1 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/12/from-ingress-to-gateway-why-you-need-istio-gateways-on-kubernetes-platforms/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;From Ingress to CRD: why my solution needs Istio Gateways on Kubernetes platforms&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Infrastructure deployment in Terraform 1/2</title><link>https://static.digihunch.com/2021/08/scalable-infrastructure-deployment-in-terraform/</link><pubDate>Wed, 11 Aug 2021 21:44:00 -0400</pubDate><guid>https://static.digihunch.com/2021/08/scalable-infrastructure-deployment-in-terraform/</guid><description>&lt;p class="wp-block-paragraph"&gt;Terraform is an excellent Infrastructure-as-Code (IaC) tool based on Hashicorp Configuration Language (HCL). Compared to JSON or YAML based declarative templates (e.g. CloudFormation and ARM), HCL is more concise, thanks to the flexibility of HCL. On the other hand, HCL is not as flexible as general purpose languages. For that sake, I see HCL as semi-declarative IaC. This post is my notes about best practices with Terraform development, from the context of AWS, but also applies to other cloud platforms.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Complex Types&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are three primitive types (string, number and bool) that forms collection types and structural types. Here are some common ones:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;list: element may repeat, and order is maintained:&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;orange&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;banana&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;orange&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;apple&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;set: elements are unique and unordered&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;apple&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;banana&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;orange&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;tuple: each element has its own type&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;a&amp;#34;&lt;/span&gt;, 15, true&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;object: defined by a schema with named attributes each with its own type&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;John&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; age &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;52&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;list of object&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; alloc_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;0b7271a3219bc1fc2&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; subnet_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;0c02af76c2c3e46fa&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; alloc_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;0440c334c48d4247f&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; subnet_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;02652e69fa2a71de8&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;map of string&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; property &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; attribute &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;bar&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;map of object&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; objkey1 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; alloc_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;0b7271a3219bc1fc2&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; subnet_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;0c02af76c2c3e46fa&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; objkey2 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; alloc_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;0440c334c48d4247f&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; subnet_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;02652e69fa2a71de8&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Whenever applicable, Terraform converts types implicitly or explicitly. For example, when a list or tuple is converted to set, all elements are converted to string and duplicates are removed. Object and map are very similar. Map of string can be converted to object if the attributes comply with the schema. Additional attributes not in the schema are discarded.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;HCL Types is similar to Python&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Although being totally different beasts, the complex types between HCL and Python are similar, to the point I suspect the HCL design is influenced by Python. I summarize the similarities as such:&lt;/p&gt;&#10;&lt;figure class="wp-block-table"&gt;&lt;table class="has-fixed-layout"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Python Types&lt;/td&gt;&lt;td&gt;list []&lt;/td&gt;&lt;td&gt;tuple ()&lt;/td&gt;&lt;td&gt;set {}&lt;/td&gt;&lt;td&gt;dict {}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Terraform Types&lt;/td&gt;&lt;td&gt;list []&lt;/td&gt;&lt;td&gt;tuple []&lt;/td&gt;&lt;td&gt;set []&lt;/td&gt;&lt;td&gt;map {}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ordered&lt;/td&gt;&lt;td&gt;Y&lt;br&gt;You can access item by index&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;td&gt;N &lt;br&gt;you cannot access an item by index or key; however you can loop over all itmes&lt;/td&gt;&lt;td&gt;N&lt;br&gt;key-value pair that allows you to access item by key&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;changeable (mutable)&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;td&gt;N&lt;br&gt;You cannot update, add or remove items&lt;/td&gt;&lt;td&gt;Y&lt;br&gt;Add or remove only. no change to existing elements&lt;/td&gt;&lt;td&gt;Keys must remain unique or the values get overwritten;&lt;br&gt;Values are mutable&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;allow duplicate elements&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;td&gt;Y&lt;/td&gt;&lt;td&gt;N&lt;/td&gt;&lt;td&gt;Keys must be unique; values don&amp;#8217;t have to&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Python, list and tuple allow elements of mixed types but in IaC like Terraform we mostly don&amp;#8217;t need mixed types. In Terraform, an object is a map without a defined type. In most situations, lists and tuples behave identically, as do maps and objects.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another area of similarity is with the comprehension of list and dict/maps. In Python for example, &lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;## Supposed you need to create a list:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;lst&lt;span style="color:#f92672"&gt;=&lt;/span&gt;[]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; x &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lst&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(x&lt;span style="color:#f92672"&gt;**&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;print(lst)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;### that can be simplified as the following to create the list:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;lst &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [x&lt;span style="color:#f92672"&gt;**&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; x &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;)]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;## You can even add contidion&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;even_numbers&lt;span style="color:#f92672"&gt;=&lt;/span&gt;[num &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; num &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; num&lt;span style="color:#f92672"&gt;%&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&lt;span style="color:#f92672"&gt;==&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;## You can introduce function calls:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;words &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;hello&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;world&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;python&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;list&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;comprehension&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;lengths &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [len(word) &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; word &lt;span style="color:#f92672"&gt;in&lt;/span&gt; words]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;## You can even combine two lists&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;lst1&lt;span style="color:#f92672"&gt;=&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;lst2&lt;span style="color:#f92672"&gt;=&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#39;c&amp;#39;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#39;d&amp;#39;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pair&lt;span style="color:#f92672"&gt;=&lt;/span&gt;[[i,j] &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; i &lt;span style="color:#f92672"&gt;in&lt;/span&gt; lst1 &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; j &lt;span style="color:#f92672"&gt;in&lt;/span&gt; lst2]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;print(pair)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;## With dict, it&amp;#39;s similar&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;evens&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{x:x&lt;span style="color:#f92672"&gt;**&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; x &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; x&lt;span style="color:#f92672"&gt;%&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&lt;span style="color:#f92672"&gt;==&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;print(evens)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In Terraform, we use similar techniques:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; s &lt;span style="color:#f92672"&gt;in&lt;/span&gt; var&lt;span style="color:#f92672"&gt;.&lt;/span&gt;list : upper(s)] &lt;span style="color:#75715e"&gt;# build a tuple/list from a list&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; k, v &lt;span style="color:#f92672"&gt;in&lt;/span&gt; var&lt;span style="color:#f92672"&gt;.&lt;/span&gt;map : length(k) &lt;span style="color:#f92672"&gt;+&lt;/span&gt; length(v)] &lt;span style="color:#75715e"&gt;# build a list from a map&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; s &lt;span style="color:#f92672"&gt;in&lt;/span&gt; var&lt;span style="color:#f92672"&gt;.&lt;/span&gt;list : s &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; upper(s)} &lt;span style="color:#75715e"&gt;# build a map from a list&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; s &lt;span style="color:#f92672"&gt;in&lt;/span&gt; var&lt;span style="color:#f92672"&gt;.&lt;/span&gt;list : upper(s) &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; s &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;] &lt;span style="color:#75715e"&gt;# build a tuple/list from a list with condition &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Note that the documentation of Terraform doesn’t explicitly call them out as comprehensions. However, it&amp;#8217;s exactly the same idea as comprehensions in Python. Even the &lt;code&gt;range()&lt;/code&gt; function exists both in Python and Terraform.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="modularization"&gt;Modularization&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Modules allows you to group related resources together. They can also be re-used and called by other modules. It is fairly straightforward to create a module:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;put the resource declarations into a sub-directory&lt;/li&gt;&#10;&lt;li&gt;define input and output in the directory&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;However, the introduction of module complicates the directory structure and variable referencing, which is important to take into account before starting creating modules. This &lt;a href="https://www.terraform.io/docs/language/modules/develop/index.html#when-to-write-a-module"&gt;guideline&lt;/a&gt; has further discussion about when to create a module. I re-wrote the terraform templates in &lt;a href="https://github.com/digihunch/orthweb/tree/main/terraform"&gt;Orthweb project&lt;/a&gt; to leverage modularization wherever possible, but there is still some stand-alone resource (e.g. random_id) not belonging to any module. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To reference resources across modules, you need to import those resources (using &lt;a href="https://www.terraform.io/docs/language/data-sources/index.html"&gt;data source&lt;/a&gt;) from within the module. There are a couple of ways. You may pass the argument of data source as input variable, or you can leverage the filter capability of data source. Let&amp;#8217;s look at one example of each mechanism.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the example below, we import a subnet by subnet id:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;data&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;aws_subnet&amp;#34; &amp;#34;private_subnet&amp;#34;&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt;.&lt;span style="color:#66d9ef"&gt;private_subnet_id&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In the example below, we import a subnet by filtering from all subnets in the VPC by tag:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;data&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;aws_subnet&amp;#34; &amp;#34;private_subnet&amp;#34;&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vpc_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt;.&lt;span style="color:#66d9ef"&gt;vpc_id&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;filter&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;tag:Name&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; values &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;Private&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;There are pros and cons of each approach. A module with mechanism 1 is more transferrable across different environment, because the ID of subnet is explicitly provided. However, authors needs to manage those explicit variables with code. Mechanism 2 fetches target resources with filter. It depends on a well-implemented tagging policy in the resource farm.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Terraform &lt;a href="https://registry.terraform.io/"&gt;Registry&lt;/a&gt; (since 2017) contains a lot of pre-built modules for each backend platform (e.g. &lt;a href="https://registry.terraform.io/browse/modules?provider=aws"&gt;AWS&lt;/a&gt;). If you find any module that can be used in your project, the module repo can be referenced directly by Git repository URL. You should be aware of the risk of this practice though. Many platforms are keen to publish modules for their platform. Anyone can &lt;a href="https://www.terraform.io/docs/language/modules/develop/publish.html"&gt;publish their own modules&lt;/a&gt; to the community as well.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You can quickly generate module documentation with &lt;a href="https://terraform-docs.io/"&gt;terraform-docs&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="local-execution"&gt;Local Execution&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Local execution is the basic workflow mode which is mostly seen with very small collaboration team. In this mode, the developer executes terraform binary (Terraform CLI) from their workstation (e.g. Laptop). The Terraform CLI converts code into API calls to interface cloud provider. The most frequently used commands (from terraform directory) are:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;terraform init&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;terraform plan&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;terraform apply&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The init command initializes the working directory. The plan command figures out the delta between code and infrastructure. It outlines the changes it is about to make. The apply command commits the change. The documentation of Terraform CLI commands is &lt;a href="https://www.terraform.io/docs/cli/commands/index.html"&gt;here&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Terraform keeps track of the infrastructure it manages in state file. &lt;a href="https://www.terraform.io/docs/language/state/purpose.html"&gt;This&lt;/a&gt; article explains the purpose of state. State management collaboration difficult with local execution because the state file by default is created in the working directory on user&amp;#8217;s workstation. Although the state file can be configured to be stored in a shared location such as S3, it still requires a mechanism to &lt;a href="https://www.terraform.io/docs/language/state/locking.html"&gt;lock&lt;/a&gt; the state in a multi-developer collaboration.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In large operations, the same code base in Terraform, is usually used to created several different sets of infrastructures, for example, in different geographic regions. So it is a 1-to-many relationship between the code repo and the infrastructure state. To further complicate things, each state might have been deployed using different revisions of the code. To overcome that challenge, Terraform introduced the concept of &lt;a href="https://www.terraform.io/docs/language/state/workspaces.html"&gt;workspace&lt;/a&gt;, which is essentially an instance of state describing a particular group of infrastructure being managed by the same source code. When there are many workspaces, it becomes tricky to manage them with CLI &lt;a href="https://www.terraform.io/docs/cli/commands/workspace/index.html"&gt;commands&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;State management is a major challenge that needs to be solved for team collaboration in local execution workflow. Each state must use the same revision of Terraform code. You can use Git in combination as a workaround to that limitation but the point is you cannot tie a workspace to a commit with the workspace &lt;a href="https://www.terraform.io/docs/cli/commands/workspace/index.html"&gt;commands&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In some enterprise environment, the execution is from a VM (e.g. ADO agent on-premise) without Internet access, which poses another challenge. First, we need to pre-load required providers manually. The enterprise needs a proxy solution to safely download packages from &lt;a href="https://releases.hashicorp.com/terraform/"&gt;Hashicorp website&lt;/a&gt;. One good option is &lt;a href="https://www.sonatype.com/products/repository-oss"&gt;Nexus Repository&lt;/a&gt;, with both open-source and pro supports. It is a full-function artifactory repo that can host helm repo, apt repo, yum repo, etc. Second, we also need to configure Terraform so it picks up providers locally. Managing &lt;a href="https://www.terraform.io/cli/plugins"&gt;plugins&lt;/a&gt; without Internet access requires understanding of the order in which Terraform tries to load plugins during initialization. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="remote-execution"&gt;Remote Execution&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In remote execution, the code is executed in Terraform Enterprise or Terraform Cloud. Both are remote web servers. The difference is that Terraform Enterprise is self-hosted service, requiring IT specialist to &lt;a href="https://www.terraform.io/docs/cli/commands/workspace/index.html"&gt;install&lt;/a&gt; and maintain Terraform Enterprise. Terraform Cloud on the other hand, is a managed SaaS service. The &lt;a href="https://www.hashicorp.com/products/terraform/pricing"&gt;pricing model&lt;/a&gt; includes a free plan for small number of users.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" width="1696" height="1004" src="https://static.digihunch.com/wp-content/uploads/2021/07/image.png" alt="" class="wp-image-2486" style="width:683px;height:404px"/&gt;&lt;figcaption class="wp-element-caption"&gt;Terraform workspace configuration&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Terraform Enterprise or Cloud, the remote execution is organized in workspaces. You need to create an organization, and then create workspace under the organization in order to execute code. With each workflow, you can specify version control system (VCS) and subdirectory, to tell the workspace where to fetch Terraform code from. The workspace also allows you to define secrets and variables specific to the workspace. When you execute a workspace plan, the secrets and variables are passed from workspace to the execution logic.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" width="1480" height="1056" src="https://static.digihunch.com/wp-content/uploads/2021/07/image-1.png" alt="" class="wp-image-2487" style="width:533px;height:380px"/&gt;&lt;figcaption class="wp-element-caption"&gt;Terraform Workspace Variable configuration&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You will also need to design the Terraform code in a way to work seamlessly with the secrets and variables loaded from the workspace. The variable declaration in code should match the definition in workspace. There are already a number of variables that came in handy. Check out this &lt;a href="https://www.terraform.io/docs/cli/config/environment-variables.html"&gt;guide&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Each execution is referred to as a &amp;#8220;run&amp;#8221;, with its own run id. A workspace involves may runs, which may succeed or fail. Each run pulls a specific commit of the source repository, and goes through stages such as plan, and apply. The UI from each run result list out the status of each result, in a very easy to read format.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="2318" height="1046" src="https://static.digihunch.com/wp-content/uploads/2021/07/image-2.png" alt="" class="wp-image-2489"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The state data is persisted in the web server as they were generated. Therefore the collaborator do not need to worry about managing state with CLI tools. If there are files that you do not want picked up by the execution engine, their locations can be added to a file .terraformignore. Refer to &lt;a href="https://www.terraform.io/docs/language/settings/backends/remote.html#excluding-files-from-upload-with-terraformignore"&gt;this&lt;/a&gt; guide.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="aws-profile"&gt;AWS profile&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Local execution still has a lot of use cases in enterprises such as testing with temporary resources. A common challenge is authentication. As discussed, Terraform CLI picks up identity information from AWS CLI and authenticates its way into the backend to run API calls against. So AWS CLI must be configured correctly with the sufficient permission to provision resources. On the other hand, enterprises usually offload IAM to an identity store, such as AzureAD, Okta, etc. Putting those together, the pattern of authentication and authorization usually looks like this:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;User logs on via SSO (e.g. &lt;a href="https://static.digihunch.com/2020/03/saml-security-assertion-markup-language/"&gt;SAML&lt;/a&gt;). The validation response gives a name of an IAM role.&lt;/li&gt;&#10;&lt;li&gt;Upon successful authentication, user takes the IAM role. The role does not have any capability, except for assuming a second IAM role.&lt;/li&gt;&#10;&lt;li&gt;The second IAM role (the functional role) grants user the permission to do its business.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The steps above, can be carried out in AWS console, or with AWS cli using &lt;a href="https://docs.aws.amazon.com/cli/latest/reference/sts/assume-role.html"&gt;assume-role&lt;/a&gt; command. However, when we put Terraform in the picture, it becomes a little involving because the credential information is updated whenever the functional role is assumed, and the assume-role command takes a pretty long argument.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To skip typing the long command every time, there are some handy tools, such as &lt;a href="https://github.com/sportradar/aws-azure-login"&gt;aws-azure-login&lt;/a&gt;. An even better tool that works with a variety of identity stores is &lt;a href="https://github.com/Versent/saml2aws"&gt;saml2aws&lt;/a&gt;. The tool allows you to configure identity backend, assume the functional role, and update credential information in aws credential file, all with a single command. The AWS CLI configuration reads:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;region &lt;span style="color:#f92672"&gt;=&lt;/span&gt; us&lt;span style="color:#f92672"&gt;-&lt;/span&gt;east&lt;span style="color:#f92672"&gt;-&lt;/span&gt;1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;output &lt;span style="color:#f92672"&gt;=&lt;/span&gt; json&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cli_history &lt;span style="color:#f92672"&gt;=&lt;/span&gt; enabled&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cli_pager &lt;span style="color:#f92672"&gt;=&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;role_session_name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; functional_operation&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;profile function_user&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;source_profile &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;role_session_name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; functional_operation&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;role_arn &lt;span style="color:#f92672"&gt;=&lt;/span&gt; arn:aws:iam::9998887766:role&lt;span style="color:#f92672"&gt;/&lt;/span&gt;admin&lt;span style="color:#f92672"&gt;-&lt;/span&gt;access&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;region &lt;span style="color:#f92672"&gt;=&lt;/span&gt; us&lt;span style="color:#f92672"&gt;-&lt;/span&gt;east&lt;span style="color:#f92672"&gt;-&lt;/span&gt;1&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In Terraform provider, we need to tell it to assume that role as well:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;provider &lt;span style="color:#e6db74"&gt;&amp;#34;aws&amp;#34;&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; region &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;us-east-1&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; assume_role {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; role_arn &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;arn:aws:iam::9998887766:role/admin-access&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; session_name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;terraform&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This will ensure Terraform assumes appropriate role before doing its job.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="aws-ec2-ssh-key-pair"&gt;AWS EC2 SSH Key Pair&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;RSA key authentication for SSH should be used for Linux Instances. When creating an EC2 instance, we give it our public key so we can then later authenticate through SSH. If the key is already stored in AWS, we just need to tell EC2 the name of the key, in the key_name property. If the code is likely to be executed from several different places by different users, then we can write the code so it picks up public key from user&amp;#8217;s workstation (~/.ssh/id_rsa.pub). Here is an example:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;variable &lt;span style="color:#e6db74"&gt;&amp;#34;local_pubkey_file&amp;#34;&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type &lt;span style="color:#f92672"&gt;=&lt;/span&gt; string&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;~/.ssh/id_rsa.pub&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;data &lt;span style="color:#e6db74"&gt;&amp;#34;local_file&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;pubkey&amp;#34;&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; filename &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pathexpand(var.&lt;span style="color:#a6e22e"&gt;local_pubkey_file&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;resource &lt;span style="color:#e6db74"&gt;&amp;#34;aws_key_pair&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;user-pubkey&amp;#34;&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; key_name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;runner-pubkey&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public_key &lt;span style="color:#f92672"&gt;=&lt;/span&gt; data.&lt;span style="color:#a6e22e"&gt;local_file&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;pubkey&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;content&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;resource &lt;span style="color:#e6db74"&gt;&amp;#34;aws_instance&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;bastion&amp;#34;&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; instance_type &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;t2.micro&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; key_name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; aws_key_pair.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;pubkey.&lt;span style="color:#a6e22e"&gt;key_name&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ......&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;For remote execution, we can even add an option to pass public key in as variable, to override the key file variable. For an example, check out my &lt;a href="https://github.com/digihunch/orthweb/tree/main/terraform"&gt;orthweb&lt;/a&gt; project.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To upload files to EC2 instance from Terraform execution environment, we can use the file provisioner with ssh as connection type. &lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2021/07/helm-configuration-management-for-kubernetes-resources/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Helm – Configuration Management for Kubernetes Resources&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/08/docker-desktop-a-single-node-kubernetes-cluster/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Single-node Kubernetes cluster – docker desktop&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>