<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.0">Jekyll</generator><link href="https://cloudlad.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://cloudlad.io/" rel="alternate" type="text/html" /><updated>2022-03-13T19:04:54+00:00</updated><id>https://cloudlad.io/feed.xml</id><title type="html">cloudlad.io</title><subtitle>Technologist / Certified Kubernetes Administrator / Terraform Assosiate / Hashicorp ambassador (2020/2021)
</subtitle><author><name>Nikola</name></author><entry><title type="html">Google Cloud Workload Identity with Kubernetes and Terraform</title><link href="https://cloudlad.io/google-cloud-workload-identity-with-kubernetes-and-terraform" rel="alternate" type="text/html" title="Google Cloud Workload Identity with Kubernetes and Terraform" /><published>2022-03-02T00:00:00+00:00</published><updated>2022-03-02T00:00:00+00:00</updated><id>https://cloudlad.io/gcp-workload-identity-with-gke-and-terraform</id><content type="html" xml:base="https://cloudlad.io/google-cloud-workload-identity-with-kubernetes-and-terraform">&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;a href=&quot;https://trixelized.itch.io/&quot;&gt;
    &lt;img alt=&quot;pixel_art_forest&quot; title=&quot;Pixel Art Forest&quot; src=&quot;assets/images/2022_03_pixelized_forest.jpg&quot; style=&quot;border:1px solid #2C4D56&quot; height=&quot;460&quot; /&gt;
  &lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“The only truly secure system is one that is powered off, cast in a block of concrete and sealed in a lead-lined room with armed guards.” - Gene Spafford&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;br /&gt;
In this blog post, I will write about improving one’s security posture by running Google Kubernetes Engine (GKE) workloads on the Google Cloud Platform (GCP). My focus falls mainly on authentication: the section between the Identity and Access Management (IAM) service and the GKE workloads. But first, I need to set the stage.&lt;/p&gt;

&lt;h2 id=&quot;service-accounts-iam-roles-and-key-pairs&quot;&gt;&lt;a href=&quot;#service-accounts-iam-roles-and-key-pairs&quot;&gt;Service Accounts, IAM Roles, and Key Pairs&lt;/a&gt;&lt;/h2&gt;

&lt;h4 id=&quot;service-accounts&quot;&gt;&lt;a href=&quot;#service-accounts&quot;&gt;Service Accounts&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Service accounts are types of accounts typically used by applications or so-called workloads.
With a service account, the application can authenticate to other GCP resources or APIs. It is an entity defined by GCP and resides in the IAM service. Its email address representation, e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sa-name@gcp-project-id.iam.gserviceaccount.com&lt;/code&gt;, is unique to a given GCP account.&lt;/p&gt;

&lt;h4 id=&quot;iam-roles&quot;&gt;&lt;a href=&quot;#iam-roles&quot;&gt;IAM Roles&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;A service account is usually granted an IAM role that defines the authorization scope for other GCP resources.
With that said, we can safely assume that a service account deals with authentication and the IAM role deals with authorization.&lt;/p&gt;

&lt;h4 id=&quot;public-private-key-pairs&quot;&gt;&lt;a href=&quot;#public-private-key-pairs&quot;&gt;Public-Private Key Pairs&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Each service account correlates with a public/private RSA key pair.
Depending on how the private key is generated, it diverges into two types:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Google-managed key pairs.&lt;/li&gt;
  &lt;li&gt;User-managed key pairs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h6 id=&quot;google-managed-key-pairs&quot;&gt;&lt;a href=&quot;#google-managed-key-pairs&quot;&gt;Google-Managed Key pairs&lt;/a&gt;&lt;/h6&gt;
&lt;p&gt;Are managed by Google, and only the public key can be viewed and verified.
The most common use of such keys is for generating short-lived credentials.
They are automatically rotated and used for signing and are valid for a maximum of two weeks.
Services like App Engine and Compute Engine use Google-managed key pairs.
They are my preferred method (it should be everyone’s default). That is because I do not have to care about managing or storing any private keys which are considered sensitive data.&lt;/p&gt;

&lt;h6 id=&quot;user-managed-key-pairs&quot;&gt;&lt;a href=&quot;#user-managed-key-pairs&quot;&gt;User-Managed Key pairs&lt;/a&gt;&lt;/h6&gt;
&lt;p&gt;Are created and obtained by the user upon request.
The private key is called a “service account key” and uses the JSON data format. See Figure 1.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
🚫 &lt;b&gt;Never share, nor publicly expose your service account key!&lt;/b&gt; 🚫
&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;service_account&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;project_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;some-gcp-project-id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;private_key_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;313eqeq21321dsadsadqwe21213wewdasdaasdws&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;private_key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-----BEGIN PRIVATE KEY-----&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;-----END PRIVATE KEY-----&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;client_email&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cloudlad@some-gcp-project-id.iam.gserviceaccount.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;client_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;30348941921832193121&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;auth_uri&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://accounts.google.com/o/oauth2/auth&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;token_uri&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://oauth2.googleapis.com/token&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;auth_provider_x509_cert_url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://www.googleapis.com/oauth2/v1/certs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;client_x509_cert_url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://www.googleapis.com/robot/v1/metadata/x509/cloudlad%40some-gcp-project-id.iam.gserviceaccount.com&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 1. A user-managed bogus service account key.&lt;/b&gt;
&lt;/p&gt;

&lt;p&gt;This User-managed key by default has no expiration date. It also inherits the same permissions as the service account.
Naturally, such keys are considered a security risk. The responsibility for them falls on the user. See Figure 2.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
    &lt;img alt=&quot;service account key&quot; title=&quot;service account key&quot; src=&quot;assets/images/2022_03_service_account_key.png&quot; style=&quot;border:1px solid #2C4D56&quot; /&gt;
&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 2. Places where a service account key might exist.&lt;/b&gt;
&lt;/p&gt;

&lt;h2 id=&quot;why-use-service-account-keys&quot;&gt;&lt;a href=&quot;#why-use-service-account-keys&quot;&gt;Why use service account keys?&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;One of the more common use cases for service accounts is for accessing resources in GCP, be it a Compute Engine VM or a GKE node pool.
For clarification, a node pool is a group of VMs sharing the same configuration.&lt;/p&gt;

&lt;p&gt;The pitfall here is, in the case of the GKE node pools, all of the pods on the nodes will inherit the permissions assigned to the service account.
Let’s assume that application A needs read-only access to PubSub, and application B needs write access to Memorystore (Redis).
The applications are both running as pods on nodes in a node pool.
If we attach a service account on the node pool level, both applications will inherit the permissions from the attached service account.  Both will have unnecessarily broad permissions, increasing the attack surface and risk.
The example is only with two applications.
A GKE cluster hosts way more, not counting the system pods.&lt;/p&gt;

&lt;p&gt;The solution is rather obvious, do not assign permissions on the node pool level.&lt;/p&gt;

&lt;p&gt;If I cannot do it on the node level, I would have to do it on the application level. E.g., secrets loaded directly in the pod.
The secrets API object in K8s holds a small amount of sensitive data. The data gets put into a running pod or container image, either mounted or exposed as an environment variable.&lt;/p&gt;

&lt;p&gt;With this, I am back to square one, user-generated service account keys loaded into pods.&lt;/p&gt;

&lt;h2 id=&quot;workload-identity&quot;&gt;&lt;a href=&quot;#workload-identity&quot;&gt;Workload Identity&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Workload Identity is a process that enables workloads to impersonate (IAM) service accounts to access GCP services.
The workloads might or might not be running on GCP.
In simpler terms, the workloads assume the authorization scope of a service account and use short-lived credentials that the user does not need to manage in any way. Effectively preventing the leakage of credentials and decreasing the possible attack surface. See Figure 3.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;workload-identity-level-1&quot; title=&quot;Workload Identity Level 1&quot; src=&quot;assets/images/2022_03_level_1.png&quot; style=&quot;border:1px solid #2C4D56&quot; /&gt;
&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 3. Simplified representation of Workload Identity.&lt;/b&gt;
&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Let me explain this in a bit more detail.
By enabling Workload Identity on a cluster, a K8s DaemonSet is deployed, with a pod in the host network.
That pod dubbed the GKE metadata server (MDS), runs in the kube-system namespace and intercepts all requests to the GCP metadata server from the other pods (workloads).
The pods running on the host network are the exception.&lt;/p&gt;

&lt;p&gt;The whole authentication flow consists of a few steps.&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;The GKE MDS requests OpenID Connect (OIDC) signed JSON Web Tokens (JWT) from the Kubernetes API server.&lt;/li&gt;
  &lt;li&gt;The GKE MDS then requests an access token for the K8s Service Account.&lt;/li&gt;
  &lt;li&gt;IAM validates the bindings and OIDC and returns an access token to the MDS.&lt;/li&gt;
  &lt;li&gt;That access token will be sent back to IAM, and a short-lived GCP service account token will be issued.&lt;/li&gt;
  &lt;li&gt;It is then returned to the GKE MDS and passed on to the workload.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally, the pod can use the short-lived service account token to access GCP resources.
See Figure 4.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;workload-identity-level-3&quot; title=&quot;Workload Identity Level 3&quot; src=&quot;assets/images/2022_03_level_3.png&quot; style=&quot;border:1px solid #2C4D56&quot; /&gt;
&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 4. Workload Identity.&lt;/b&gt;
&lt;/p&gt;

&lt;h2 id=&quot;practical-example-in-terraform&quot;&gt;&lt;a href=&quot;#practical-example-in-terraform&quot;&gt;Practical Example In Terraform&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;What good is theory without the practical part?&lt;/p&gt;

&lt;p&gt;For that, I will be using Terraform and GCP to show the GKE Workload Identity in action.&lt;/p&gt;

&lt;p&gt;From this point on, I will assume that the reader has the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;https://github.com/parabolic/examples/blob/master/terraform/gke_workload_identity/main.tf#L1-L2&quot;&gt;Terraform 1.1 or older&lt;/a&gt; and gcloud CLI installed.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Functional GCP credentials (hopefully not configured via a user-managed service account key), authorized for creating and managing GCP projects, including all of its underlying resources.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ve made a &lt;a href=&quot;https://github.com/parabolic/examples/tree/master/terraform/gke_workload_identity&quot;&gt;Terraform root module&lt;/a&gt; that creates a:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;GCP project (it can also reside under an Organization folder).&lt;/li&gt;
  &lt;li&gt;VPC with private subnets.&lt;/li&gt;
  &lt;li&gt;Private GKE Cluster with Workload Identity enabled.&lt;/li&gt;
  &lt;li&gt;Node pool with a Workload Identity enabled in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GKE_METADATA&lt;/code&gt; mode. &lt;a href=&quot;https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#option_1_node_pool_creation_with_recommended&quot;&gt;Google recommends&lt;/a&gt; including this attribute, so node creation fails if Workload Identity is not enabled on the cluster.&lt;/li&gt;
  &lt;li&gt;A shell script named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k8s.sh&lt;/code&gt; for the creation and configuration of resources inside the GKE cluster.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See Figure 5.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;workload-identity-level-2.5&quot; title=&quot;Workload Identity Level 2.5&quot; src=&quot;assets/images/2022_03_level_2.5.png&quot; style=&quot;border:1px solid #2C4D56&quot; /&gt;
&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 5. Simplified representation of the cloud resources created by the Terraform root module.&lt;/b&gt;
&lt;/p&gt;

&lt;p&gt;For more details about the module and its configuration, refer to the &lt;a href=&quot;https://github.com/parabolic/examples/blob/master/terraform/gke_workload_identity/README.md&quot;&gt;README.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I would have preferred to create all of the resources in one go, but unfortunately, it is not possible with Terraform’s CRUD cycle. The problem is that the K8s provider cannot authenticate against the GKE cluster in one apply run. That makes perfect sense because the GKE cluster is not present in the application stage, and the K8s provider cannot authenticate.
To programmatically create all of the resources, I use the &lt;a href=&quot;https://github.com/parabolic/examples/blob/master/terraform/gke_workload_identity/file.tf#L5-L19&quot;&gt;local_file&lt;/a&gt; provider to generate a helper bash script from a Terraform template. The bash script &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k8s.sh&lt;/code&gt; will appear in the working directory of the root module after a successful apply run. It can be &lt;a href=&quot;https://github.com/parabolic/examples/blob/master/terraform/gke_workload_identity/variables.tf#L20-L23&quot;&gt;overiden to any valid path&lt;/a&gt; in the Operating System.
When Terraform apply finishes, the k8s.sh shell script will be present in the working directory. When executed, it will create the cloudlad namespace, a service account, and the annotation for the said service account. Lastly, it will start a pod with the annotated service account attached.&lt;/p&gt;

&lt;p&gt;Because of the pod spec override with the annotated service account, the pod itself will inherit the attached role to the &lt;a href=&quot;https://github.com/parabolic/examples/blob/master/terraform/gke_workload_identity/iam.tf#L7-L12&quot;&gt;IAM service account&lt;/a&gt; from the Terraform root module. See Figure 6.&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-ex&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; pipefail &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; nounset

&lt;span class=&quot;nv&quot;&gt;K8S_NAMESPACE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cloudlad&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;K8S_SERVICE_ACCOUNT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cloudlad&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;GCP_REGION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;europe-west3&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;CLUSTER_NAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cloudlad-2-ringtail&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;GCP_PROJECT_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;workload-identity-2-ringtail&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;K8S_CONTEXT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;gke_workload-identity-2-ringtail_europe-west3_cloudlad-2-ringtail&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;GCP_SERVICE_ACCOUNT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cloudlad-2-ringtail-k8s@workload-identity-2-ringtail.iam.gserviceaccount.com&quot;&lt;/span&gt;

gcloud container clusters get-credentials &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$CLUSTER_NAME&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$GCP_PROJECT_ID&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--region&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$GCP_REGION&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

kubectl config use-context &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$K8S_CONTEXT&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

kubectl create namespace &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$K8S_NAMESPACE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

kubectl create serviceaccount &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$K8S_SERVICE_ACCOUNT&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$K8S_NAMESPACE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

kubectl annotate serviceaccount &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$K8S_SERVICE_ACCOUNT&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$K8S_NAMESPACE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    iam.gke.io/gcp-service-account&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$GCP_SERVICE_ACCOUNT&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

kubectl run &lt;span class=&quot;nt&quot;&gt;--image&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;google/cloud-sdk:slim &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--namespace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$K8S_NAMESPACE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--overrides&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;{ 'spec': { 'serviceAccount': &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$K8S_SERVICE_ACCOUNT&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; } }&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    cloudlad &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;infinity
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 6. Helper shell script for programmatically creating the K8s resources, valid only for a single Terraform state.&lt;/b&gt;
&lt;/p&gt;

&lt;p&gt;When I execute the k8s.sh script, the K8s Service Account will get annotated with a key: iam.gke.io/gcp-service-account and a value: cloudlad-2-ringtail-k8s@workload-identity-2-ringtail.iam.gserviceaccount.com. The annotation enables the K8s service account to inherit the same Authorization scope from the IAM Service account. See Figure 7.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;kubectl&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;-n&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;cloudlad&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;serviceaccounts&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;cloudlad&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;-o&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;apiVersion&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;kind&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ServiceAccount&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;annotations&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;iam.gke.io/gcp-service-account&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cloudlad-2-ringtail-k8s@workload-identity-2-ringtail.iam.gserviceaccount.com&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;creationTimestamp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2022-02-22T21:28:59Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cloudlad&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;namespace&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cloudlad&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;resourceVersion&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;9849&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;uid&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;f29f981f-28fc-4ca2-ba35-31dd42ff8e7a&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;secrets&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cloudlad-token-thlzr&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 7. Service Account annotation with kubectl.&lt;/b&gt;
&lt;/p&gt;

&lt;p&gt;You might ask yourself, “OK, what now?” Checking whether the coupling between GCP IAM and GKE works is straightforward. The newly created pod runs indefinite sleep. And the container is started from the official GCP SDK image, meaning the gcloud CLI is readily available.&lt;/p&gt;

&lt;p&gt;I will execute an interactive shell in the running container. Then check the available service accounts, and finally, create a PubSub topic. It is all possible because the pod inherits the &lt;a href=&quot;https://github.com/parabolic/examples/blob/master/terraform/gke_workload_identity/iam.tf#L10&quot;&gt;roles/pubsub.admin&lt;/a&gt; role. See Figure 8.1.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl &lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--stdin&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--tty&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; cloudlad cloudlad &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; bash
root@cloudlad:/# curl &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Metadata-Flavor: Google&quot;&lt;/span&gt; http://169.254.169.254/computeMetadata/v1/instance/service-accounts/
cloudlad-2-ringtail-k8s@workload-identity-2-ringtail.iam.gserviceaccount.com/
default/
root@cloudlad:/# gcloud pubsub topics create cloudlad
Created topic &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;projects/workload-identity-2-ringtail/topics/cloudlad].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 8.1 Creating a PubSub Topic with Workload Identity from a running pod.&lt;/b&gt;
&lt;/p&gt;

&lt;p&gt;The informational message from the gcloud CLI confirms that the creation of the PubSub topic is without issues. Checking the GCP console whether the PubSub topic is present wouldn’t hurt. See figure 8.2.&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;pubsub_topic&quot; title=&quot;PubSub Topic&quot; src=&quot;assets/images/2022_03_pubsub_topic.png&quot; style=&quot;border:1px solid #2C4D56&quot; /&gt;
&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 8.2 PubSub Topic created with Workload Identity in the GCP console.&lt;/b&gt;
&lt;/p&gt;

&lt;p&gt;And with this, I have confirmed that Workload Identity is set up and is working.&lt;/p&gt;

&lt;p&gt;That is all. I hope that this blog post demystifies GCP’s Workload Identity a bit. As always, feel free to contact me with any ideas, info, and suggestions. Don’t forget that sharing is caring!&lt;/p&gt;</content><author><name>Nikola</name></author><category term="kubernetes" /><category term="k8s" /><category term="gcp" /><category term="google" /><category term="cloud" /><category term="iam" /><category term="access" /><category term="pods" /><category term="serviceaccounts" /><summary type="html">The only truly secure system is one that is powered off, cast in a block of concrete and sealed in a lead-lined room with armed guards.</summary></entry><entry><title type="html">Terraform, The Less-Common Commands And Options</title><link href="https://cloudlad.io/terraform-the-less-common-commands-and-options" rel="alternate" type="text/html" title="Terraform, The Less-Common Commands And Options" /><published>2021-09-06T00:00:00+00:00</published><updated>2021-09-06T00:00:00+00:00</updated><id>https://cloudlad.io/terraform-uncommon-options</id><content type="html" xml:base="https://cloudlad.io/terraform-the-less-common-commands-and-options">&lt;p align=&quot;center&quot;&gt;
  &lt;a href=&quot;https://unsplash.com/photos/Yraw_pJcAoo&quot;&gt;
    &lt;img alt=&quot;desert&quot; title=&quot;Desert&quot; src=&quot;assets/images/2021_09_planet_earth.jpg&quot; align=&quot;middle&quot; width=&quot;600&quot; height=&quot;350&quot; style=&quot;border:1px solid #2C4D56&quot; /&gt;
  &lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Once a new technology rolls over you, if you’re not part of the steamroller, you’re part of the road.” - Stewart Brand&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;The most frequently used Terraform commands, &lt;strong&gt;plan&lt;/strong&gt;, &lt;strong&gt;apply&lt;/strong&gt;, and &lt;strong&gt;destroy&lt;/strong&gt;, are the first things I learned when I started using Terraform. Primarily because those commands are a crucial part of Terraform’s cycle. Over time, as Terraform and its community grew, the features of Terraform grew in parallel. Now there are a lot more commands, options, arguments, and functions. Some are not used as often, and some do not even show when invoking the help menu.&lt;/p&gt;

&lt;p&gt;In this blog post, I want to talk about the not-so-very common commands, arguments, and use cases.&lt;/p&gt;

&lt;h2 id=&quot;terraform-console&quot;&gt;&lt;a href=&quot;#terraform-console&quot;&gt;Terraform Console&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;This command is my current personal fav, the &lt;strong&gt;console&lt;/strong&gt; command. As per documentation it is most commonly used for experimenting with expressions and functions. As an added extra, if the command is invoked in a folder with an active Terraform state, it will read it from the configured backend. Inspecting and experimenting with the state with Terraform’s console can be fun!&lt;/p&gt;

&lt;p&gt;Getting to it is as simple as typing in the command line:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;terraform console
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we can start “playing” with our state, provided there is something in it that we can fiddle with.&lt;/p&gt;

&lt;p&gt;For the sake of this blog post let’s create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.tf&lt;/code&gt; file with the following contents. The file is also available in my &lt;a href=&quot;https://github.com/parabolic/examples/tree/master/terraform/the_less_common_commands_and_options&quot;&gt;examples repo&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-hcl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;locals&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;local_file&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;private_key&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;content&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;jsonencode&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;local&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;filename&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;./map.json&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Needless to say, executing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform init&lt;/code&gt; is necessary to pull the plugins and initialize the configured backend if there is any.&lt;/p&gt;

&lt;p&gt;After, running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform apply&lt;/code&gt; against this template, a JSON encoded file with the content of the local variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt; will be created in the same folder. That is easy enough.&lt;/p&gt;

&lt;p&gt;But what if I needed a YAML encoded file? I am also not sure how it will look. I prefer JSON, but I also like to have some comments.&lt;/p&gt;

&lt;p&gt;I can check it on the fly by firing up Terraform console in the same folder and just YAML encoding the local &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt; variable:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;terraform console
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; yamlencode&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;local.map&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOT&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
&quot;a&quot;: 1
&quot;b&quot;: 2
&quot;c&quot;: 3
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;
EOT
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Voilà! We see how our file will look like if we’ve used the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yamlencode&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Why stop there, maybe I just need the value of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt; key?&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; yamlencode&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;local.map.b&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOT&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
2
...
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;
EOT
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Swapping the keys and the values is also something that I might want?&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; yamlencode&lt;span class=&quot;o&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;k,v &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;local.map : v &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; k &lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOT&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
&quot;1&quot;: &quot;a&quot;
&quot;2&quot;: &quot;b&quot;
&quot;3&quot;: &quot;c&quot;
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;
EOT
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Or just “trying” out the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try&lt;/code&gt; function to see how it works exactly.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; try&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;yamlencode&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;local.map.d&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;, 4&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Case in point. The flexibility that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform console&lt;/code&gt; provides when testing, troubleshooting, or simply playing around is immense.&lt;/p&gt;

&lt;h2 id=&quot;shell-autocomplete&quot;&gt;&lt;a href=&quot;#shell-autocomplete&quot;&gt;Shell Autocomplete&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;This is one of the commands that do not appear when I invoke Terraform via the CLI. Having worked with Terraform for quite a while myself, I stumbled upon this command fairly recently. It lets you use shell tab-completion. As of writing this blog post, it works with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bash&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zsh&lt;/code&gt; shells only.&lt;/p&gt;

&lt;p&gt;Running this will enable autocomplete for Terraform CLI. Re-reading the shell configuration or re-reloading a shell will activate it.&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;terraform &lt;span class=&quot;nt&quot;&gt;-install-autocomplete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;lock-timeout&quot;&gt;&lt;a href=&quot;#lock-timeout&quot;&gt;Lock Timeout&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;This is another very useful option, especially when running in an automated environment, such as a CI system. Imagine the scenario: we start two or more plans or apply jobs at a similar time. Naturally, Terraform will fail if it cannot get a lock for the state. With the option &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-lock-timeout&lt;/code&gt; instead of immediately failing, Terraform will retry getting the state lock for the duration specified in seconds. If Terraform cannot acquire a lock after the timeout has passed, it will fail with an error.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;terraform apply/plan &lt;span class=&quot;nt&quot;&gt;-lock-timeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;180s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;manual-state-manipulation&quot;&gt;&lt;a href=&quot;#manual-state-manipulation&quot;&gt;Manual State Manipulation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Usually, I edit the state via Terraform CLI. Sometimes, though, I need to manually edit it. I do it via a pull, edit, and push cycle. These commands will work regardless of the type of backend configured.
The most important things to remember when manually editing a state are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Validate the JSON before pushing.&lt;/li&gt;
  &lt;li&gt;Increment the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serial&lt;/code&gt; key by one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;pull-edit-push&quot;&gt;“Pull-Edit-Push”&lt;/h4&gt;

&lt;p&gt;The following command will pull and write the current active state to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform_state.json&lt;/code&gt; file.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;terraform state pull &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; terraform_state.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Which has the following contents.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;terraform_version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;1.0.4&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;serial&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;lineage&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;b538a863-c14e-c216-9309-e5e35f91468c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;outputs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;resources&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mode&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;managed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;local_file&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;private_key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;provider&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;provider[&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;registry.terraform.io/hashicorp/local&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;instances&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;schema_version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;attributes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;{&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:1,&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:2,&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:3}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content_base64&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;directory_permission&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0777&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;file_permission&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0777&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;filename&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;./map.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;e7ec4a8f2309bdd4c4c57cb2adfb79c91a293597&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;sensitive_content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;source&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;sensitive_attributes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;private&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;bnVsbA==&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I will remove the last element in the content parameter, and now the local state file looks like this:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;terraform_version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;1.0.4&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;serial&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;lineage&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;b538a863-c14e-c216-9309-e5e35f91468c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;outputs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;resources&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mode&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;managed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;local_file&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;private_key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;provider&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;provider[&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;registry.terraform.io/hashicorp/local&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;instances&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;schema_version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;attributes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;{&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:1,&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:2}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content_base64&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;directory_permission&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0777&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;file_permission&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0777&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;filename&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;./map.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;e7ec4a8f2309bdd4c4c57cb2adfb79c91a293597&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;sensitive_content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;source&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;sensitive_attributes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;private&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;bnVsbA==&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After saving the file, “Pushing” the state back to its configured backend is as easy as:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;terraform state push ./terraform_state.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The “push” will fail if there’s something wrong with the state file. Otherwise, it shouldn’t provide any output.&lt;/p&gt;

&lt;p&gt;Further verifying that the state edit is successful can be done by running either &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform plan&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform apply&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The changes that Terraform will report attest to the successful manual state edit.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;terraform apply
local_file.private_key: Refreshing state... &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;e7ec4a8f2309bdd4c4c57cb2adfb79c91a293597]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;create replacement

Terraform will perform the following actions:

  &lt;span class=&quot;c&quot;&gt;# local_file.private_key must be replaced&lt;/span&gt;
-/+ resource &lt;span class=&quot;s2&quot;&gt;&quot;local_file&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;private_key&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      ~ content              &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; jsonencode&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
          ~ &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
              + c &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 3
                &lt;span class=&quot;c&quot;&gt;# (2 unchanged elements hidden)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# forces replacement&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
      ~ &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;                   &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;e7ec4a8f2309bdd4c4c57cb2adfb79c91a293597&quot;&lt;/span&gt; -&amp;gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;known after apply&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c&quot;&gt;# (3 unchanged attributes hidden)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

Plan: 1 to add, 0 to change, 1 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only &lt;span class=&quot;s1&quot;&gt;'yes'&lt;/span&gt; will be accepted to approve.

  Enter a value: &lt;span class=&quot;nb&quot;&gt;yes

&lt;/span&gt;local_file.private_key: Destroying... &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;e7ec4a8f2309bdd4c4c57cb2adfb79c91a293597]
local_file.private_key: Destruction &lt;span class=&quot;nb&quot;&gt;complete &lt;/span&gt;after 0s
local_file.private_key: Creating...
local_file.private_key: Creation &lt;span class=&quot;nb&quot;&gt;complete &lt;/span&gt;after 0s &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;e7ec4a8f2309bdd4c4c57cb2adfb79c91a293597]

Apply &lt;span class=&quot;nb&quot;&gt;complete&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; Resources: 1 added, 0 changed, 1 destroyed.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As expected, Terraform reverts back to the state as instructed by the configuration in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.tf&lt;/code&gt; template.&lt;/p&gt;

&lt;h2 id=&quot;json-instead-of-hcl&quot;&gt;&lt;a href=&quot;#json-instead-of-hcL&quot;&gt;JSON Instead Of HCL&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The use case for this capability is quite interesting. I can programmatically generate Terraform templates using the JSON syntax. The only requirement is for the filename to have the extension &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tf.json&lt;/code&gt;. Otherwise, Terraform will not pick the configuration up.&lt;/p&gt;

&lt;p&gt;I can convert the example above to JSON like so:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;locals&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;map&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;resource&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;local_file&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;private_key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;jsonencode(local.map)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;filename&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;./map.json&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I would reckon it’s unusual to generate a JSON encoded file using the JSON syntax.&lt;/p&gt;

&lt;p&gt;It is a very appealing feature I imagine is being actively used out there.&lt;/p&gt;

&lt;h2 id=&quot;terraform-graph&quot;&gt;&lt;a href=&quot;#terraform-graph&quot;&gt;Terraform Graph&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;Not the most widely used Terraform command, but most definitely uncommon. It generates a DOT (graph description language) file that I can use to render an image in a well-known format, such as PNG.
Running this command in the example state yields a chart description configuration.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;terraform graph
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-dot highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;digraph&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;compound&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;true&quot;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;newrank&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;true&quot;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;subgraph&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;root&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;s2&quot;&gt;&quot;[root] local_file.private_key (expand)&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;local_file.private_key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;box&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
		&lt;span class=&quot;s2&quot;&gt;&quot;[root] provider[\&quot;registry.terraform.io/hashicorp/local\&quot;]&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;provider[\&quot;registry.terraform.io/hashicorp/local\&quot;]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shape&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;diamond&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
		&lt;span class=&quot;s2&quot;&gt;&quot;[root] local_file.private_key (expand)&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;[root] local.map (expand)&quot;&lt;/span&gt;
		&lt;span class=&quot;s2&quot;&gt;&quot;[root] local_file.private_key (expand)&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;[root] provider[\&quot;registry.terraform.io/hashicorp/local\&quot;]&quot;&lt;/span&gt;
		&lt;span class=&quot;s2&quot;&gt;&quot;[root] meta.count-boundary (EachMode fixup)&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;[root] local_file.private_key (expand)&quot;&lt;/span&gt;
		&lt;span class=&quot;s2&quot;&gt;&quot;[root] provider[\&quot;registry.terraform.io/hashicorp/local\&quot;] (close)&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;[root] local_file.private_key (expand)&quot;&lt;/span&gt;
		&lt;span class=&quot;s2&quot;&gt;&quot;[root] root&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;[root] meta.count-boundary (EachMode fixup)&quot;&lt;/span&gt;
		&lt;span class=&quot;s2&quot;&gt;&quot;[root] root&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;[root] provider[\&quot;registry.terraform.io/hashicorp/local\&quot;] (close)&quot;&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If I “pipe” this through a filter for drawing directed graphs:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;terraform graph | dot &lt;span class=&quot;nt&quot;&gt;-Tpng&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; graph.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I get a nice image of a graph as seen in Figure 1. (I’ve also inverted the colors with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;imagemagick&lt;/code&gt;)&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img style=&quot;border:1px solid #2C4D56&quot; alt=&quot;Graph created from a dot template&quot; title=&quot;Graph created from a dot template&quot; src=&quot;assets/images/2021_09_dot.png&quot; height=&quot;300&quot; /&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 1. Graph created from a DOT template.&lt;/b&gt;
&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Getting to know a specific technology or a tool is not as easy as it seems. Hopefully, this blog post will make some of the unusual Terraform options clearer and more usable.&lt;/p&gt;

&lt;p&gt;If you enjoyed this blog post, please share and spread it. For any suggestions or questions, contact me at &lt;a href=&quot;mailto:nikola@cloudlad.io&quot;&gt;nikola@cloudlad.io&lt;/a&gt;.&lt;/p&gt;</content><author><name>Nikola</name></author><category term="terraform" /><category term="commands" /><category term="console" /><category term="uncommon" /><category term="console" /><category term="cli" /><category term="cloud" /><summary type="html">Once a new technology rolls over you, if you're not part of the steamroller, you're part of the road.</summary></entry><entry><title type="html">IaC Pipelines With Terraform And Cloud Build</title><link href="https://cloudlad.io/iac-pipelines-with-terraform-and-cloud-build" rel="alternate" type="text/html" title="IaC Pipelines With Terraform And Cloud Build" /><published>2021-04-07T00:00:00+00:00</published><updated>2021-04-07T00:00:00+00:00</updated><id>https://cloudlad.io/iac-pipelines-with-terraform-and-cloud-build</id><content type="html" xml:base="https://cloudlad.io/iac-pipelines-with-terraform-and-cloud-build">&lt;p align=&quot;center&quot;&gt;
  &lt;a href=&quot;https://unsplash.com/photos/rA6fTRJUdEc?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditShareLink&quot;&gt;
    &lt;img alt=&quot;pipes&quot; title=&quot;Pipes&quot; src=&quot;assets/images/2021_04_pipes.png&quot; align=&quot;middle&quot; /&gt;
  &lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Today, most software exists, not to solve a problem, but to interface with other software.” — IO Angell&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;br /&gt;
In this blog post I’ll be showcasing &lt;a href=&quot;https://cloud.google.com/build&quot;&gt;Cloud Build&lt;/a&gt;, a CI/CD service from &lt;a href=&quot;https://cloud.google.com/&quot;&gt;Google Cloud Platform&lt;/a&gt;, evaluating its capabilities and how well it works with Infrastructure as Code (IaC) deployment pipelines.&lt;/p&gt;

&lt;h2 id=&quot;infrastructure-as-code-iac&quot;&gt;&lt;strong&gt;Infrastructure as Code (IaC)&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;Chances are you’ve come across the term IaC in your career. Undoubtedly it is the default process for managing and provisioning computational resources. The capability of defining declarative code and storing it in a version control system makes it even more appealing. Not to mention that the process can be automated, tested, re-used, and reviewed. That eventually leads to decreased risk, failure, but an increase in speed, stability, and security.&lt;/p&gt;

&lt;p&gt;Simply put, long gone are the days where one would order a plethora of parts just to build a few servers, only to realize that the rack doesn’t fit in the server hall.
With IaC and modern Infrastructure as a Service (IaaS) providers, we have almost infinite computational power at our fingertips, if our finances are on par.&lt;/p&gt;

&lt;h2 id=&quot;gitops&quot;&gt;&lt;strong&gt;GitOps&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;I can describe GitOps as a natural evolution of IaC that leverages Git as the single source of truth. We request changes with the creation of Pull Requests, which triggers various status checks after every commit. When the changes meet the conditions set for the repository, and we have approved the Pull Request, we can merge it back into the main/master branch. This should automatically trigger an event that deploys the changes into the environments that run the workloads, Figure 1.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;pipeline&quot; title=&quot;Pipeline&quot; src=&quot;assets/images/2021_04_pipeline.png&quot; /&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 1. Simplified GitOps pipeline.&lt;/b&gt;
&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;
With my preferred IaC tool Terraform, the main/master branch should report no changes when run against the infrastructure. It should always reflect the state of the currently running resources. This way we ensure the consistency of the Git Flow, which is especially important when collaborating within a team.&lt;/p&gt;

&lt;h2 id=&quot;cloud-build-and-terraform&quot;&gt;&lt;strong&gt;Cloud Build and Terraform&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;But what good is a deploy pipeline without a CI/CD service? Cloud Build comes with full CI/CD capability with which we can automate Terraform.&lt;/p&gt;

&lt;p&gt;One of the challenges of the engineering teams is avoiding the creation of a so-called “silo”. It is prominent when working with infrastructure. A “silo” happens when the generic processes that apply for engineering do not apply to IaC that might lead to the unintentional creation of a “silo”. Tools like Terraform and Cloud Build help in bridging that gap or at least solving part of it.&lt;/p&gt;

&lt;p&gt;Security is a subject that we must not neglect. It plays a big part in the CI/CD service selection. Having owner-level permission for a Google Cloud Project is something that I want to avoid managing and sharing via hard-coded secrets.&lt;/p&gt;

&lt;p&gt;I am about to show a setup that has a lot of moving parts. The concepts and components are listed below for clarity. Head on to the &lt;a href=&quot;#implementation&quot;&gt;Implementation&lt;/a&gt; section if you are already familiar with them.&lt;/p&gt;

&lt;h2 id=&quot;concepts&quot;&gt;&lt;strong&gt;Concepts&lt;/strong&gt;&lt;/h2&gt;
&lt;h5 id=&quot;terraform-state&quot;&gt;&lt;strong&gt;Terraform State&lt;/strong&gt;&lt;/h5&gt;

&lt;p&gt;A Terraform state is an object that keeps track of the managed infrastructure and configuration. It can be both local and remote. Remote states are more or less a requirement, especially when working in a team.
To use a remote state, we have to solve the “Chicken or the egg” problem. E.g., If we want to put the Terraform state on a GCP storage bucket, we first need to create that bucket with Terraform and then migrate the state. Another peculiarity is state locking. Terraform automatically locks the state for all operations that could write to it, thus preventing others from running Terraform. The need for a queuing mechanism is obvious.&lt;/p&gt;

&lt;h5 id=&quot;principle-of-least-privilege-polp&quot;&gt;&lt;strong&gt;Principle of Least Privilege (PoLP)&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;PoLP is a concept in which we give an entity a minimum level of access. It should have access to only the information needed for its purpose.&lt;/p&gt;

&lt;h5 id=&quot;inert-vs-active-terraform-states&quot;&gt;&lt;strong&gt;Inert vs. Active Terraform States&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;This concept stems from the Terraform’s behaviour. Big states that manage a lot of resources are slow to plan/apply. Together with state locking and PoLP, the need for splitting the states is clear. These so-called “Inert States” change less often than the “Active States”. E.g., we control the creation of GCP projects with one state but manage the resources of said projects in other states.&lt;/p&gt;

&lt;p&gt;Furthermore, both Inert and Active states require a different level of permissions.&lt;/p&gt;

&lt;p&gt;This approach brings the attack surface and blast radius to a minimum, but increases the speed of operation and security.&lt;/p&gt;

&lt;h2 id=&quot;implementation&quot;&gt;&lt;strong&gt;Implementation&lt;/strong&gt;&lt;/h2&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;implementation&quot; title=&quot;implementation&quot; src=&quot;assets/images/2021_04_pipeline_cloud_build.png&quot; /&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 2. IaC pipeline with Cloud Build.&lt;/b&gt;
&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;
Let us have a look at the following &lt;a href=&quot;https://github.com/parabolic/examples/tree/master/terraform/iac-pipelines-with-terraform-and-cloud-build&quot;&gt;directory structure&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
├── projects &lt;span class=&quot;c&quot;&gt;# Applied manually, creates the GCP projects&lt;/span&gt;
└── workloads
    ├── prod-1549784393 &lt;span class=&quot;c&quot;&gt;# Applied automatically, creates resources in prod-1549784393 GCP project&lt;/span&gt;
    └── stag-3380426388 &lt;span class=&quot;c&quot;&gt;# Applied automatically, creates resources in stag-3380426388 GCP project&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are three Terraform states. The folder names under the workloads directory correspond to the names of the GCP projects inside the projects directory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;projects&lt;/code&gt; - Inert Terraform State&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;projects&lt;/strong&gt; folder holds the state for creating all GCP projects: &lt;strong&gt;cloud-build-3660853213&lt;/strong&gt;, &lt;strong&gt;prod-1549784393&lt;/strong&gt;, &lt;strong&gt;stag-3380426387&lt;/strong&gt; that must be applied manually. It requires permissions for creating GCP projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prod-1549784393&lt;/code&gt; - Active Terraform State&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;prod-1549784393&lt;/strong&gt; folder holds the state of the resources in the &lt;strong&gt;prod-1549784393&lt;/strong&gt; GCP project. Cloud Build runs it automatically. It requires &lt;a href=&quot;https://cloud.google.com/iam/docs/understanding-roles#basic-definitions&quot;&gt;owner permissions&lt;/a&gt; for the project &lt;strong&gt;prod-1549784393&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stag-3380426388&lt;/code&gt; - Active Terraform State&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;stag-3380426388&lt;/strong&gt; folder holds the state of the resources in the &lt;strong&gt;stag-3380426388&lt;/strong&gt; GCP project. Cloud Build runs it automatically. It requires &lt;a href=&quot;https://cloud.google.com/iam/docs/understanding-roles#basic-definitions&quot;&gt;owner permissions&lt;/a&gt; for the project &lt;strong&gt;stag-3380426388&lt;/strong&gt;.&lt;/p&gt;

&lt;h5 id=&quot;cloud-build-triggers&quot;&gt;&lt;strong&gt;Cloud Build triggers&lt;/strong&gt;&lt;/h5&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;cloud_build_triggers&quot; title=&quot;Cloud_Build_Triggers&quot; src=&quot;assets/images/2021_04_cloud_build_triggers.png&quot; /&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 3. Cloud Build Triggers.&lt;/b&gt;
&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;
There are &lt;a href=&quot;https://github.com/parabolic/examples/tree/master/terraform/iac-pipelines-with-terraform-and-cloud-build&quot;&gt;two Cloud Build triggers&lt;/a&gt;, dubbed &lt;strong&gt;pull-request-push&lt;/strong&gt; and &lt;strong&gt;pull-request-merge&lt;/strong&gt;. As the names imply, they get triggered upon creation/pushing or merging a pull request into master. pull-request-push is the development phase and pull-request-merge is the deployment phase.
For more information on how to use Cloud Build triggers, consult &lt;a href=&quot;https://cloud.google.com/build/docs/configuring-builds/create-basic-configuration&quot;&gt;GCP’s documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Only fmt, validate, and plan run as build steps for simplicity’s sake. Nothing prevents us from adding more checks or tests, though.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pull-request-push (Development phase)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cloud Build starts this trigger upon creation or push to a PR. It executes two steps, Terraform checks (fmt, validate) and Terraform plan (init, plan).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pull-request-merge (Deployment phase)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cloud Build starts this trigger upon push/merge to master. It executes three steps, Terraform checks (fmt, validate), Terraform plan (init, plan) and Terraform apply. The apply is running a plan file from the previous step to make sure it executes the changes the previous step, Figure 6.&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;terraform-apply'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;waitFor&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;terraform-init-plan'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;alpine:3.13.3'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;terraform_plan_files'&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/var/terraform_plan_files'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;terraform/iac-pipelines-with-terraform-and-cloud-build/workloads'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;entrypoint&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;sh'&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-c'&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;set -eo pipefail&lt;/span&gt;

        &lt;span class=&quot;s&quot;&gt;apk add git bash curl&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;git clone https://github.com/tfutils/tfenv.git /opt/.tfenv&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;ln -s /opt/.tfenv/bin/* /usr/local/bin&lt;/span&gt;

        &lt;span class=&quot;s&quot;&gt;for folder in $(ls .); do&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;echo &quot;+-------------------------------------------+&quot;&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;echo &quot;Applying state ${folder} &quot;&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;echo &quot;+-------------------------------------------+&quot;&lt;/span&gt;

          &lt;span class=&quot;s&quot;&gt;cd $folder&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;tfenv install&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;terraform init&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;terraform apply \&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;-auto-approve \&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;/var/terraform_plan_files/${folder}_${BUILD_ID}_${PROJECT_NUMBER}.plan&quot;&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;cd -&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;done&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 6. Cloud Build &quot;terraform-apply&quot; step.&lt;/b&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terraform versioning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I am using Alpine Linux for the running environment. The reason for that is easier Terraform management. I constrain the minor version within the state,&lt;/p&gt;
&lt;div class=&quot;language-hcl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;terraform&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;required_version&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;gt;= 0.14&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;and I use &lt;a href=&quot;https://github.com/tfutils/tfenv&quot;&gt;tfenv&lt;/a&gt; to pin the patch version.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; .terraform-version
0.14.9
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;With this approach when updating Terraform, I do not need to change the trigger configuration files and there is no additional logic to install the correct version except executing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tfenv install&lt;/code&gt;, Figure 4.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pre-packaging a container image is also something that should be considered.&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;+-------------------------------------------+
Planning state prod-1549784393 
+-------------------------------------------+
Installing Terraform v0.14.9
Downloading release tarball from https://releases.hashicorp.com/terraform/0.14.9/terraform_0.14.9_linux_amd64.zip

Downloading SHA &lt;span class=&quot;nb&quot;&gt;hash &lt;/span&gt;file from https://releases.hashicorp.com/terraform/0.14.9/terraform_0.14.9_SHA256SUMS
No keybase &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;found, skipping OpenPGP signature verification
terraform_0.14.9_linux_amd64.zip: OK
Archive:  tfenv_download.HLdilD/terraform_0.14.9_linux_amd64.zip
  inflating: terraform
Installation of terraform v0.14.9 successful. To make this your default version, run &lt;span class=&quot;s1&quot;&gt;'tfenv use 0.14.9'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 4. Cloud Build installing Terraform with Tfenv.&lt;/b&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terraform execution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In every Cloud Build step, &lt;a href=&quot;https://github.com/parabolic/examples/blob/master/terraform/iac-pipelines-with-terraform-and-cloud-build/cloudbuild_pull_request_push.yaml#L15-L27&quot;&gt;there is a loop&lt;/a&gt; that goes to each Terraform state folder and execute the specified actions, Figure 5.&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;folder &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; .&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do
  &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;+-------------------------------------------+&quot;&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Fmt/Validate state &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;folder&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &quot;&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;+-------------------------------------------+&quot;&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$folder&lt;/span&gt;
  tfenv &lt;span class=&quot;nb&quot;&gt;install
  &lt;/span&gt;terraform &lt;span class=&quot;nb&quot;&gt;fmt&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-recursive&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-check&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-diff&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
  terraform init &lt;span class=&quot;nt&quot;&gt;-backend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;false
  &lt;/span&gt;terraform validate &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; -
&lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 5. Cloud Build step &quot;ftm-validate&quot;.&lt;/b&gt;
&lt;/p&gt;

&lt;p&gt;This is how it all looks on GitHub when working in a real-life scenario. Terraform fmt and validate were failing the checks until pushed the fixes, Figure 7.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;github_checks&quot; title=&quot;Cloud Build Triggers&quot; src=&quot;assets/images/2021_04_github_checks.png&quot; /&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 7. GitHub checks.&lt;/b&gt;
&lt;/p&gt;
&lt;h5 id=&quot;initializing-the-projects-terraform-state&quot;&gt;&lt;strong&gt;Initializing the projects Terraform state&lt;/strong&gt;&lt;/h5&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;terraform apply &lt;span class=&quot;nt&quot;&gt;-var&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;billing_account&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&amp;lt;SOME_BILLING_ACCOUNT&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ol&gt;
  &lt;li&gt;Comment the &lt;a href=&quot;https://github.com/parabolic/examples/blob/master/terraform/iac-pipelines-with-terraform-and-cloud-build/projects/main.tf#L124-L171&quot;&gt;Cloud Build triggers&lt;/a&gt;. They cannot be creation until the GitHub repository is connected.&lt;/li&gt;
  &lt;li&gt;Comment the &lt;a href=&quot;https://github.com/parabolic/examples/blob/master/terraform/iac-pipelines-with-terraform-and-cloud-build/projects/main.tf#L15-L17&quot;&gt;remote state configuration&lt;/a&gt;. We cannot have a remote state until the storage bucket is created.&lt;/li&gt;
  &lt;li&gt;Apply the Terraform state.
    &lt;ul&gt;
      &lt;li&gt;This will (amongst other things):
        &lt;ul&gt;
          &lt;li&gt;Create the three projects.&lt;/li&gt;
          &lt;li&gt;Enable the Cloud Build API.&lt;/li&gt;
          &lt;li&gt;Give Cloud Build’s service account &lt;a href=&quot;https://cloud.google.com/iam/docs/understanding-roles#basic-definitions&quot;&gt;owner permissions&lt;/a&gt; for the projects that host the workloads &lt;strong&gt;prod-1549784393&lt;/strong&gt;, and &lt;strong&gt;stag-3380426388&lt;/strong&gt;.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Connect &lt;a href=&quot;https://cloud.google.com/build/docs/automating-builds/run-builds-on-github&quot;&gt;GitHub and Cloud Build&lt;/a&gt; assuming that the GitHub repository hosts the same directory structure and template names, Figure 8.&lt;/li&gt;
  &lt;li&gt;Uncomment the &lt;a href=&quot;https://github.com/parabolic/examples/blob/master/terraform/iac-pipelines-with-terraform-and-cloud-build/projects/main.tf#L124-L171&quot;&gt;Cloud Build triggers&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Uncomment the &lt;a href=&quot;https://github.com/parabolic/examples/blob/master/terraform/iac-pipelines-with-terraform-and-cloud-build/projects/main.tf#L15-L17&quot;&gt;remote state configuration&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Init and Apply Terraform.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;github_cloud_build&quot; title=&quot;GitHub_Cloud_Build&quot; src=&quot;assets/images/2021_04_github_cloud_build.png&quot; /&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 8. GitHub Repository and Cloud Build.&lt;/b&gt;
&lt;/p&gt;

&lt;h5 id=&quot;initializing-the-prod-1549784393-stag-3380426388-terraform-states&quot;&gt;&lt;strong&gt;Initializing the prod-1549784393, stag-3380426388 Terraform states&lt;/strong&gt;&lt;/h5&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;Comment the remote state configuration in each folder/state. We cannot have a remote state until the storage bucket is created.&lt;/li&gt;
  &lt;li&gt;Init and Apply Terraform.&lt;/li&gt;
  &lt;li&gt;Uncomment the remote state configuration.&lt;/li&gt;
  &lt;li&gt;Init and Apply Terraform.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is it! With this, we have now successfully created an IaC pipeline with Cloud Build and GitHub.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Evaluating Cloud Build was a fun task and like every solution out there, it has its strengths and weaknesses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Secure
    &lt;ul&gt;
      &lt;li&gt;If you run your workloads on GCP this would be the most secure approach because the builds do not leave Google Cloud’s “ecosystem”.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Fast
    &lt;ul&gt;
      &lt;li&gt;Cleary, builds were blazingly fast even with installing third-party software during execution, Figure 9.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;File filters do not work for every event:
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;Included files&lt;/strong&gt; and &lt;strong&gt;Ignored files&lt;/strong&gt; can only be specified if Push to a branch is selected as your Event. In my case, I could only specify this filter in the &lt;strong&gt;pull-request-merge&lt;/strong&gt; trigger.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;No interactive build steps:
    &lt;ul&gt;
      &lt;li&gt;Running Terraform with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-auto-aprove&lt;/code&gt; attribute can be very dangerous an interactive approval step when applying in production would be very nice to have.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Cannot establish an SSH (Debug) session into the build environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;cloud_build_execution_times&quot; title=&quot;Cloud Build Execution times&quot; src=&quot;assets/images/2021_04_cloud_build_execution_times.png&quot; /&gt;
  &lt;b style=&quot;font-size:0.7vw&quot;&gt;Figure 9. Cloud Build Execution times.&lt;/b&gt;
&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;
That is all for now. For any suggestions or questions, feel free to contact me. If you like what I am doing, don’t forget to share this blog post.&lt;/p&gt;</content><author><name>Nikola</name></author><category term="terraform" /><category term="GCloud" /><category term="google" /><category term="cloud" /><category term="GCP" /><category term="IaaS" /><category term="Infrastructure" /><category term="infrastructure-as-code" /><category term="pipeline" /><category term="cloud-build" /><category term="IaC" /><summary type="html">Today, most software exists, not to solve a problem, but to interface with other software.</summary></entry><entry><title type="html">Terraform 0.13 and Google Cloud</title><link href="https://cloudlad.io/terraform-0-13-and-google-cloud" rel="alternate" type="text/html" title="Terraform 0.13 and Google Cloud" /><published>2020-09-29T00:00:00+00:00</published><updated>2020-09-29T00:00:00+00:00</updated><id>https://cloudlad.io/terraform-0-13-and-google-cloud</id><content type="html" xml:base="https://cloudlad.io/terraform-0-13-and-google-cloud">&lt;p align=&quot;center&quot;&gt;
  &lt;a href=&quot;https://ansimuz.com/site/&quot;&gt;
    &lt;img alt=&quot;paralax&quot; title=&quot;Paralax&quot; src=&quot;assets/images/2020_09_29_paralax_background.png&quot; height=&quot;350px&quot; align=&quot;middle&quot; /&gt;
  &lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If you’re going to do operations reliably, you need to make it reproducible and programmatic. - Mike Loukides&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;terraform&quot;&gt;&lt;strong&gt;Terraform&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;Terraform, the de facto configuration tool for managing infrastructure nowadays has come a long way since its early release, roughly 5 years ago. After many iterations, mostly driven by vocal supporters, one of Terraform’s biggest strengths, listening to its community has proven crucial to its success.&lt;/p&gt;

&lt;p&gt;This blog post is going to be about how I practically apply &lt;a href=&quot;https://www.hashicorp.com/blog/announcing-hashicorp-terraform-0-13&quot;&gt;Terraform’s 0.13&lt;/a&gt; features into one of the major IaaS providers &lt;a href=&quot;https://cloud.google.com/&quot;&gt;Google Cloud&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;the-scenario&quot;&gt;&lt;strong&gt;The Scenario&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;Having worked with both AWS and GCP, I have found that as usual, there are pros and cons to both cloud providers.&lt;/p&gt;

&lt;p&gt;One slightly simpler thing (perhaps better) than the other cloud providers is the coupling between G Suite and GCP. G Suite acts as a central identity store that we can use in GCP’s Cloud Identity and Access Management (IAM). The coupling between the users in a company and its IaaS provider simplifies the management overhead. As an example, one can use G Suite emails or email groups to assign permissions to resources within GCP. This leads to interesting combinations that clearly define responsibilities, and we benefit from heightened security because of said segregation of roles and duties.&lt;/p&gt;

&lt;p&gt;Google Cloud has a concept of &lt;a href=&quot;https://cloud.google.com/resource-manager/docs/cloud-platform-resource-hierarchy&quot;&gt;Cloud Platform Resource Hierarchy&lt;/a&gt;. This enables the operator to control and fine-tune ownership, access control, and inheritance. An organization is the root node of the hierarchy. Then come the &lt;a href=&quot;https://cloud.google.com/resource-manager/docs/creating-managing-folders&quot;&gt;folders&lt;/a&gt; and the projects. See Figure 1.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;gcp_org_folders_projects&quot; title=&quot;Gcp Org Folders Projects&quot; src=&quot;assets/images/2020_09_29_gcp_org_folders_projects.png&quot; style=&quot;border:0.5px solid grey&quot; align=&quot;middle&quot; /&gt;
&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
Figure 1: Cloud Platform Resource Hierarchy
&lt;/p&gt;

&lt;p&gt;Let us assume the following case. There is a big organization with many engineering teams that need isolated testing environments. The security team that needs to supervise the teams and their respective resources needs to have a project to deploy its tools. This is to provide support in the unlikely case of security issues that might arise.&lt;/p&gt;

&lt;p&gt;A Folder can have many other folders and projects below it. Besides, access to the resources within the projects can be limited by Folder so that an IAM email (user, group, or a service account) can have a set of permissions for the Cloud resources living within a Folder. With some planning, the hierarchical structure of an organization can be fascinating.&lt;/p&gt;

&lt;p&gt;For simplicity’s sake, I will limit the example with only a subset of the resources. See Figure 2.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;gcp_org_folders_projects_subset&quot; title=&quot;Gcp Org Folders Projects subset&quot; src=&quot;assets/images/2020_09_29_gcp_org_folders_projects_subset.png&quot; style=&quot;border:0.5px solid grey&quot; align=&quot;middle&quot; /&gt;
&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  Figure 2: Subset of resources
  &lt;/p&gt;

&lt;p&gt;From this point on, the reader should be comfortable working with Terraform.&lt;/p&gt;

&lt;h3 id=&quot;count-and-for_each-for-modules&quot;&gt;&lt;strong&gt;count&lt;/strong&gt; And &lt;strong&gt;for_each&lt;/strong&gt; For Modules&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;count&lt;/strong&gt; and &lt;strong&gt;for_each&lt;/strong&gt; features enable us to have a systematic creation of multiple resource instances with a single configuration block. With previous Terraform versions, it was not possible to use these arguments with modules. Starting from 0.13, these powerful meta-arguments for workflows add up to the already extensive list of Terraform’s features.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Both &lt;strong&gt;count&lt;/strong&gt; and &lt;strong&gt;for_each&lt;/strong&gt; can be used interchangeably. I prefer &lt;strong&gt;for_each&lt;/strong&gt; because the resource instances in &lt;strong&gt;count&lt;/strong&gt; are identified by their index in a list. If we remove an element from the middle of the list, every item after it would require a re-creation. Read more in the &lt;a href=&quot;https://www.terraform.io/docs/configuration/resources.html#when-to-use-for_each-instead-of-count&quot;&gt;official Terraform documentation&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We can set a &lt;a href=&quot;https://github.com/parabolic/examples/blob/master/terraform/0.13/main.tf#L1-L35&quot;&gt;local map variable&lt;/a&gt; in the root module that defines the creation of our resources, bundled inside the child module. See Figure 3.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;a href=&quot;https://github.com/parabolic/examples/blob/master/terraform/0.13/main.tf#L1-L47&quot;&gt;
    &lt;img alt=&quot;local map variable&quot; title=&quot;Local Map Variable&quot; src=&quot;assets/images/2020_09_29_gcp_org_local_map_variable.png&quot; height=&quot;800px&quot; style=&quot;border:0.5px solid grey&quot; align=&quot;middle&quot; /&gt;
  &lt;/a&gt;
  &lt;p align=&quot;center&quot;&gt;
  Figure 3: Local Map Variable
  &lt;/p&gt;
&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
This Terraform configuration will create three folders and four projects. See Figure 4.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;a&gt;
    &lt;img alt=&quot;folders_projects&quot; title=&quot;Folders Projects&quot; src=&quot;assets/images/2020_09_29_gcp_org_folders_projects_console.png&quot; height=&quot;200px&quot; style=&quot;border:0.5px solid grey&quot; align=&quot;middle&quot; /&gt;
  &lt;/a&gt;
  &lt;p align=&quot;center&quot;&gt;
  Figure 4: Folders, projects in GCP console
  &lt;/p&gt;
&lt;/p&gt;

&lt;h3 id=&quot;depends_on-for-modules&quot;&gt;&lt;strong&gt;depends_on&lt;/strong&gt; For Modules&lt;/h3&gt;

&lt;p&gt;Provisioning APIs is a sophisticated business. Sometimes the APIs do not behave as we expect them to, or often they lack features. Managing resources in the cloud adds to the complexity plus sometimes we require working with the resources in a specific order. The &lt;strong&gt;depends_on&lt;/strong&gt; meta-argument addresses the issue.&lt;/p&gt;

&lt;p&gt;There are two types of resource dependencies in Terraform, implicit or explicit. Implicit dependencies are handled by Terraform automatically. It analyses expressions within configuration blocks for references to other resources. As an example, a VM instance will implicitly depend on the resource defined in the network_interface setting, because it requires an IP Address to operate.&lt;/p&gt;

&lt;p&gt;Sometimes, though, Terraform cannot resolve some dependencies on its own. In such a case, we explicitly set them with the &lt;strong&gt;depends_on&lt;/strong&gt; meta-argument.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;depends_on&lt;/strong&gt; was originally available for resources only, from version 0.13 onwards it is available for modules as well.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let us suppose that the security team in our organization wants to have an overview of all the resources that are present along with their activity. For that, a separate GCP project would suffice. The only requirement for this project is that it needs to be created before the others (and configure its tooling accordingly). From Terraform’s perspective, all the other projects depend on this project, which I dubbed “Team-X”. See Figure 5.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;a href=&quot;https://github.com/parabolic/examples/blob/master/terraform/0.13/main.tf#L37-L70&quot;&gt;
    &lt;img alt=&quot;data structures&quot; title=&quot;Data Structures&quot; src=&quot;assets/images/2020_09_29_explicit_dependencies.png&quot; height=&quot;600px&quot; width=&quot;auto&quot; style=&quot;border:0.5px solid grey&quot; allign=&quot;middle&quot; /&gt;
  &lt;/a&gt;
  &lt;p align=&quot;center&quot;&gt;
  Figure 5: Explicit module dependency
  &lt;/p&gt;
&lt;/p&gt;

&lt;h3 id=&quot;custom-validation-rules&quot;&gt;Custom Validation Rules&lt;/h3&gt;

&lt;p&gt;Terraform’s parameters, also called input variables, have type constraints by default. Version 0.13 offers an additional feature with which I can validate the content of the input variables.&lt;/p&gt;

&lt;p&gt;The variable block supports an additional validation configuration that has a condition parameter that checks if an expression is true or false. Its usage varies depending on the variable.&lt;/p&gt;

&lt;p&gt;In the example below, I am validating the required input variable &lt;strong&gt;billing_account&lt;/strong&gt;. Google Cloud’s billing accounts have the form of six alphanumeric characters, separated by a dash sign.&lt;/p&gt;

&lt;div class=&quot;language-terraform highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;variable&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;billing_account&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt;        &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;validation&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;condition&lt;/span&gt;     &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;can&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;regex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;^[[:alnum:]]{6}-[[:alnum:]]{6}-[[:alnum:]]{6}&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;billing_account&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;error_message&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Invalid billing account, please provide a valid billing account in the following format &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;XXXXX-XXXXX-XXXXX&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The validation above checks for a valid Google Cloud billing account and fails if the regex doesn’t match with the provided input variable. The content in the “error_message” argument is being displayed on stdout.&lt;/p&gt;

&lt;p&gt;Example with an invalid billing account.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;terraform apply

Error: Invalid value &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;variable

  on main.tf line 66, &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;module &lt;span class=&quot;s2&quot;&gt;&quot;cloudlad_org_security&quot;&lt;/span&gt;:
  66:   billing_account &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; var.billing_account

Invalid billing account, please provide a valid billing account &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;the
following format &lt;span class=&quot;s2&quot;&gt;&quot;XXXXX-XXXXX-XXXXX&quot;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;

This was checked by the validation rule at
../modules/resource_management/variables.tf:5,3-13.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;
Terraform’s feature list does not stop here. There are many others, but I decided to write only about the most notable ones. For a full list of features and fixes head on to &lt;a href=&quot;https://github.com/hashicorp/terraform/releases&quot;&gt;Terraform’s release changelog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I hope that this blog post helped you in understanding how a basic hierarchical setup in Google Cloud can be achieved with Terraform. Please share this post if you find it interesting and helpful.&lt;/p&gt;

&lt;p&gt;The example module that I am using in this blog post can be &lt;a href=&quot;https://github.com/parabolic/examples/tree/master/terraform/modules/resource_management&quot;&gt;found here&lt;/a&gt;.&lt;/p&gt;</content><author><name>Nikola</name></author><category term="Terraform" /><category term="0.13" /><category term="Gcloud" /><category term="google" /><category term="cloud" /><category term="GCP" /><category term="IaaS" /><category term="Infrastructure" /><category term="infrastructure-as-code" /><category term="code" /><summary type="html">If you’re going to do operations reliably, you need to make it reproducible and programmatic</summary></entry><entry><title type="html">Testing Infrastructure with Terraform And Terratest</title><link href="https://cloudlad.io/testing-infrastructure-with-terraform-and-terratest" rel="alternate" type="text/html" title="Testing Infrastructure with Terraform And Terratest" /><published>2020-05-13T00:00:00+00:00</published><updated>2020-05-13T00:00:00+00:00</updated><id>https://cloudlad.io/testing-infrastructure-with-terraform-and-terratest</id><content type="html" xml:base="https://cloudlad.io/testing-infrastructure-with-terraform-and-terratest">&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;a href=&quot;https://edermunizz.itch.io/free-pixel-art-forest&quot;&gt;
    &lt;img alt=&quot;pixel_art_forest&quot; title=&quot;Pixel art forest&quot; src=&quot;assets/images/2020_05_13_pixel_art_forest.png&quot; height=&quot;600px&quot; align=&quot;middle&quot; /&gt;
  &lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Software testing proves the existence of bugs not their absence.&lt;br /&gt; - Anonymous&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;In this blog post, I will show you how you can test your infrastructure with &lt;a href=&quot;https://registry.terraform.io/&quot;&gt;Terraform&lt;/a&gt; and &lt;a href=&quot;https://terratest.gruntwork.io/&quot;&gt;Terratest&lt;/a&gt; on &lt;a href=&quot;https://cloud.google.com/&quot;&gt;Google Cloud&lt;/a&gt;, but first,&lt;/p&gt;

&lt;h2 id=&quot;lets-talk-about-terraform-modules&quot;&gt;&lt;strong&gt;Let’s Talk About Terraform Modules&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;Terraform modules are reusable, composable, and testable components, which pack all of the complexity into a single versatile and modifiable unit. This so-called unit can be controlled by input variables that extend or change its usability and its use cases. There are many modules out there, some official, and some provided by the community. You can explore them at Terraform’s module registry: &lt;a href=&quot;https://registry.terraform.io/browse/modules&quot;&gt;https://registry.terraform.io/browse/modules&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As for creating them, the examples in the registry serve as a solid starting point as well as this guideline by Hashicorp:
&lt;a href=&quot;https://www.terraform.io/docs/modules/index.html&quot;&gt;https://www.terraform.io/docs/modules/index.html&lt;/a&gt;.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;terraform_modules&quot; title=&quot;Terraform Modules&quot; src=&quot;assets/images/2020_05_13_terraform_modules.png&quot; /&gt;
&lt;/p&gt;

&lt;h2 id=&quot;why-do-we-need-testing&quot;&gt;&lt;strong&gt;Why Do We Need Testing?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Testing code has always required a lot of additional work that I believe that most people dread, including myself. Without testing though, we’ve no way of telling if our code will behave the way it was intended to, especially at present, where code logic tends to get sophisticated in most of the cases. Terratest can test real infrastructure into a real environment, a single unit like Terraform module if you will, evaluating the resources, results and outputs immediately.&lt;/p&gt;

&lt;h2 id=&quot;setting-up-the-environment&quot;&gt;&lt;strong&gt;Setting Up The Environment&lt;/strong&gt;&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://golang.org/&quot;&gt;Golang&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://registry.terraform.io/&quot;&gt;Terraform&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://blog.golang.org/using-go-modules&quot;&gt;Golang Dependencies&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://golang.org/pkg/testing/&quot;&gt;Golang Package Testing&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://cloud.google.com/docs/authentication/getting-started&quot;&gt;Google Application Credentials&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;I will be using the latest versions of both golang and Terraform as of the time of writing this blog post&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Golang&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The instructions for installing and configuring golang are provided here: &lt;a href=&quot;https://golang.org/doc/install&quot;&gt;https://golang.org/doc/install&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terraform&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Get Terraform either by &lt;a href=&quot;https://www.terraform.io/downloads.html&quot;&gt;downloading it&lt;/a&gt; and placing it in your “bin path”, or by using &lt;a href=&quot;https://github.com/tfutils/tfenv&quot;&gt;tfenv&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vendoring dependencies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because this example is not operating within the GOPATH, we need to specify a module name with the convention:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.com/&amp;lt;YOUR_USERNAME&amp;gt;/&amp;lt;YOUR_REPO_NAME&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you want to start from scratch instead of using &lt;a href=&quot;https://github.com/parabolic/terraform_ci_cd/blob/master/test/terraform_ci_cd_test.go&quot;&gt;my example&lt;/a&gt;, follow the steps below.&lt;/p&gt;

&lt;p&gt;These are the packages I am importing.&lt;/p&gt;

&lt;div class=&quot;language-golang highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;fmt&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;testing&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;time&quot;&lt;/span&gt;

	&lt;span class=&quot;s&quot;&gt;&quot;github.com/gruntwork-io/terratest/modules/gcp&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;github.com/gruntwork-io/terratest/modules/retry&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;github.com/gruntwork-io/terratest/modules/terraform&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Create a test directory in the root of the folder if it is not there. Copy or create a file that ends in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_test.go&lt;/code&gt;, then “cd” into the “test” directory and initialize the module.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;mkdir test&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd test&lt;/span&gt;/
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-O&lt;/span&gt; https://raw.githubusercontent.com/parabolic/terraform_ci_cd/master/test/terraform_ci_cd_test.go
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;go mod init github.com/parabolic/terraform_ci_cd
go: creating new go.mod: module github.com/parabolic/terraform_ci_cd
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;go mod vendor
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The “go mod vendor” command downloads (vendors) all of the dependent packages to the same directory.&lt;/p&gt;

&lt;p&gt;We will end up with a folder structure like this:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;/
├── go.mod
├── go.sum
├── terraform_ci_cd_test.go
└── vendor
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Google Application Credentials&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As part of the last phase, we would need to provide credentials for authenticating to Google Cloud. Please follow the steps outlined in google’s documentation on how to &lt;a href=&quot;https://cloud.google.com/docs/authentication/getting-started&quot;&gt;create a service account and export its keys&lt;/a&gt;. I would recommend creating a separate service account only for running Terraform or Terratest.&lt;/p&gt;

&lt;p&gt;After we’ve got the google cloud key file onto our system, we should export an environment variable that points to the location of the file. That way both Terratest and Terraform can authenticate to Google Cloud and conduct the tests.&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GOOGLE_APPLICATION_CREDENTIALS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/home/cloudlad/.gcloud/cloudlad-project.json&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Verification&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s do a small check-up before we move on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Golang&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;go version
go version go1.14.1 linux/amd64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;GOPATH&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;printenv &lt;/span&gt;GOPATH
/go
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Terraform&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;terraform &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt;
Terraform v0.12.24
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Google Application Credentials&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;printenv &lt;/span&gt;GOOGLE_APPLICATION_CREDENTIALS
/home/cloudlad/.gcloud/cloudlad-project.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 id=&quot;how-it-all-works-together&quot;&gt;&lt;strong&gt;How It All Works Together&lt;/strong&gt;&lt;/h2&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;terraform_modules&quot; title=&quot;Terraform Modules&quot; src=&quot;assets/images/2020_05_13_terratest_terraform.png&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;Now that we have all set up and verified, we are ready to begin testing.&lt;/p&gt;

&lt;p&gt;Terratest uses golang’s testing framework, so for those familiar with it this will be straightforward. The terratest GCP module is very limited as of writing this blog post so I am unable to test a lot of other resources that GCP has to offer. For that reason, my Terraform configuration is fairly simple, which for showcasing purposes is a good thing. Hopefully, we will see more contributions to the module in the future.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you want to find out which resources are supported by Terratest, head on to &lt;a href=&quot;https://godoc.org/github.com/gruntwork-io/terratest/modules/gcp&quot;&gt;https://godoc.org/github.com/gruntwork-io/terratest/modules/gcp&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Although we can test any Terraform configuration, Terratest works the best when testing Terraform modules as single small units. That is because it is easier to create a test for small components and Terratest can control the state with &lt;a href=&quot;https://www.terraform.io/docs/configuration/variables.html&quot;&gt;input variables&lt;/a&gt;. Input variables serve as parameters, changing the behaviour of the module without changing its source code.&lt;/p&gt;

&lt;p&gt;Let us take &lt;a href=&quot;https://github.com/parabolic/terraform_ci_cd&quot;&gt;https://github.com/parabolic/terraform_ci_cd&lt;/a&gt; as an example repository that has a Terraform module. It creates a bucket, and an instance group with an “N” number of instances. These are the current input variables that are accepted by the this module:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Name&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
      &lt;th&gt;Type&lt;/th&gt;
      &lt;th&gt;Default&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Required&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;environment&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;yes&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;project_id&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;yes&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;instance_number&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;number&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;machine_type&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;n1-standard-1&quot;&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;name&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;cloudlad&quot;&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;region&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;europe-west4&quot;&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;zone&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;europe-west4-c&quot;&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;no&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Here’s how the directory structure of the Terraform module looks like.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
├── example
│   ├── main.tf
│   ├── outputs.tf
│   ├── variables.tf
│   └── versions.tf
├── LICENSE
├── main.tf
├── outputs.tf
├── README.md
├── &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;
│   ├── go.mod
│   ├── go.sum
│   ├── terraform_ci_cd_test.go
└── variables.tf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The “example” directory holds the Terraform templates needed to call and use the module. The source to the module is set to a local path &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;source = &quot;../&quot;&lt;/code&gt;, which points to the top-level directory, which holds the files of the actual Terraform module.&lt;/p&gt;

&lt;p&gt;The “test” directory contains the terratest golang code, along with the “go.mod” and “go.sum” files (the vendor directory is not checked in and it will be created after vendoring the dependencies).&lt;/p&gt;

&lt;p&gt;We can change and control any of the input variables with Terratest. As you can see every input variable is set and preferably randomized to cover all of the possible test cases.&lt;/p&gt;

&lt;div class=&quot;language-golang highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;environment&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ci&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;instanceNumber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;3&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;terratest&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;environmentName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sprintf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;%s-%s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;projectID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gcp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetGoogleProjectIDFromEnvVar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;randomRegion&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gcp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetRandomRegion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;projectID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;randomZone&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gcp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetRandomZoneForRegion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;projectID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;randomRegion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;// Relative path to the terraform configuration&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;terraformDir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;../example/&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraformDir&lt;/code&gt; variable at the end.
This is a common pattern with which we check the validity and usability of both the module and the provided example, better known as “Eating your own dog food”.&lt;/p&gt;

&lt;p&gt;The variables that were previously defined are passed onto the Terraform template with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraformOptions&lt;/code&gt;. Consequently controlling and adjusting the Terraform module to fit our needs.&lt;/p&gt;

&lt;div class=&quot;language-golang highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;terraformOptions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;terraform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;TerraformDir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;terraformDir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;Vars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}{&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;environment&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;     &lt;span class=&quot;n&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;instance_number&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instanceNumber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;project_id&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;      &lt;span class=&quot;n&quot;&gt;projectID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;region&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;          &lt;span class=&quot;n&quot;&gt;randomRegion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;zone&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;randomZone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;EnvVars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;GOOGLE_CLOUD_PROJECT&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;projectID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Going further down the test file, we see the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; statement.&lt;/p&gt;

&lt;div class=&quot;language-golang highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// Destroy all resources in any exit case&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;terraform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Destroy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;terraformOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Defer makes sure that all the resources are destroyed no matter the exit status of the function. It is commonly used for “clean-up” purposes. In our case the defer statement will issue a non-interactive &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform destroy&lt;/code&gt; for every run of the function.&lt;/p&gt;

&lt;p&gt;After the defer we specify the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform.InitAndApply&lt;/code&gt; function which will execute “terraform init, terraform get, terraform apply”, with the provided input variables.&lt;/p&gt;

&lt;div class=&quot;language-golang highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// Run terraform init and apply&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;terraform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InitAndApply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;terraformOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now the testing phase starts.&lt;/p&gt;

&lt;p&gt;Checking if the bucket exists with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gcp.AssertStorageBucketExists&lt;/code&gt; function.&lt;/p&gt;

&lt;div class=&quot;language-golang highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// Check if the bucket exists&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gcp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AssertStorageBucketExists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;environmentName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Getting the name of the instance group is done via an output, additionally validating our naming scheme.&lt;/p&gt;
&lt;div class=&quot;language-golang highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// Get the instance group name from the output&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;instanceGroupName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;terraform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;terraformOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;instance_group_name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Lastly, we check for the number of instances present within the instance group. The test will fail if the number does not match the number we have specified with the variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;instanceNumber&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-golang highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// Check the instance number&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;retry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DoWithRetry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Geting instances from, instance group&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxRetries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sleepBetweenRetries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;instances&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instanceGroup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetInstancesE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;projectID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Errorf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to get Instances: %s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;instances&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instanceNumber&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Errorf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Expected to find exactly %d Compute Instances in Instance Group but found %d.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instanceNumber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;instances&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;
We’ve covered all of the clarifications and preparations, so we can safely start our test.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go test&lt;/code&gt; recompiles each package along with any files with names matching the file pattern “&lt;/em&gt;_test.go”.*&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd test&lt;/span&gt;/
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;go &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; RUN   TestTerraformGcp
&lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; PAUSE TestTerraformGcp
&lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; CONT  TestTerraformGcp
TestTerraformGcp 2020-05-10T12:21:52Z region.go:163: Looking up all GCP regions available &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;this account
TestTerraformGcp 2020-05-10T12:21:52Z retry.go:72: Attempting to request a Google OAuth2 token
TestTerraformGcp 2020-05-10T12:21:52Z compute.go:606: Successfully retrieved default GCP client
TestTerraformGcp 2020-05-10T12:21:52Z region.go:67: Using Region asia-east1
TestTerraformGcp 2020-05-10T12:21:52Z retry.go:72: Attempting to request a Google OAuth2 token
TestTerraformGcp 2020-05-10T12:21:52Z compute.go:606: Successfully retrieved default GCP client
TestTerraformGcp 2020-05-10T12:21:53Z region.go:148: Using Zone asia-east1-c
TestTerraformGcp 2020-05-10T12:21:53Z retry.go:72: terraform &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;init &lt;span class=&quot;nt&quot;&gt;-upgrade&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
....
....
....
....
TestTerraformGcp 2020-05-10T12:26:14Z command.go:168: Destroy &lt;span class=&quot;nb&quot;&gt;complete&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; Resources: 6 destroyed.
&lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt; PASS: TestTerraformGcp &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;261.97s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
PASS
ok      github.com/parabolic/terraform_ci_cd    261.982s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If our test passed, it should conclude with output similar to the one above (the output is truncated).&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Even though the GCP module for Terratest is still in infancy, we can still use it to test out basic resources, and the more it is used the more features and better support it will have. Testing is a relevant part of the CI/CD pipeline and I hope I’ve given you a glimpse of how easy it is to start testing your infrastructure with Terraform and Terratest.&lt;/p&gt;

&lt;p&gt;Until next time! Please share this blog post if you liked it.&lt;/p&gt;</content><author><name>Nikola</name></author><category term="Terraform" /><category term="testing" /><category term="Terratest" /><category term="CI" /><category term="CD" /><category term="Gcloud" /><category term="google" /><category term="cloud" /><category term="GCP" /><category term="instances" /><category term="instance" /><category term="group" /><category term="modules" /><summary type="html">Software testing proves the existence of bugs not their absence.</summary></entry><entry><title type="html">Docker image security practices</title><link href="https://cloudlad.io/docker-image-security-practices" rel="alternate" type="text/html" title="Docker image security practices" /><published>2020-01-02T00:00:00+00:00</published><updated>2020-01-02T00:00:00+00:00</updated><id>https://cloudlad.io/docker-image-security-practices</id><content type="html" xml:base="https://cloudlad.io/docker-image-security-practices">&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;a href=&quot;https://blank-canvas.itch.io/parallax-pixel-art-background-desert&quot;&gt;
    &lt;img alt=&quot;Pixel_art_desert_day&quot; title=&quot;Pixel art desert day&quot; src=&quot;assets/images/2020_06_01_desert_day.png&quot; width=&quot;800px&quot; height=&quot;400px&quot; align=&quot;middle&quot; /&gt;
  &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;It takes 20 years to build a reputation and few minutes of cyber-incident to ruin it.&lt;br /&gt; - Stephane Nappo&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;the-state-of-cyber-security-today&quot;&gt;&lt;strong&gt;The state of cyber security today&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;Security usually means inconvenience, inconvenience brings hindrance, how do we move fast yet stay as secure as possible in this technological present. There’s no universal answer for this subject, but what we should always do at least strive, is to adhere to certain standards and principles which will reduce the risk in the unlikely case of a breach of security. Applying best security practices would be our most dependable remedy against malicious actors of any kind. As an example let’s take today’s container technologies, namely the most popular one, docker. They are fast, lightweight and immutable, but come with a tradeoff. The border that isolates the host and the container is very thin, alas thinner than the conventional bulky and slow Virtual Machines. The tradeoff, in this case, is very clear, we’ve gained enhanced utilization of resources but because the container technology shares a lot of components with the host system if we do not employ well-defined security precautions we will be putting the host system along with the other containers running on it at risk. If we can employ as many security safeguards as we can, rest assured that running a container would be more secure than the traditional Virtual Machine.&lt;/p&gt;

&lt;p&gt;How hard is it to misspell the name of the Docker registry image URL combination and get a third-party image with possible malevolent software packaged?
Or even worse, what if you mount your docker socket or run the container with full privileges?&lt;/p&gt;

&lt;p&gt;In the unfortunate event of a security breach, there’s no guessing on what will be the scope of the potential damages. The old proverb by the Irish Novelist Samuel Lover &lt;em&gt;“Better safe than sorry”&lt;/em&gt; is invaluable. There are so many tools out there that can help us build and create wonderful technologies, but when one thinks about it, everything is based on trust which we are placing on the creators and community of said tools to implement the best security practices. Sadly, in reality, that often turns out not to be the case.&lt;/p&gt;

&lt;p&gt;Looking on the positive side, we can and we should do something about it, by conforming and adhering to a set of security practices that will lower or nullify the blast radius in a potential breach, which I will elaborate further in this blog post.&lt;/p&gt;

&lt;p&gt;I will be focusing on docker mostly as it is one of the most widely used container solutions nowadays.&lt;/p&gt;

&lt;p&gt;For the examples I am using this simple golang web server code.&lt;/p&gt;

&lt;div class=&quot;language-golang highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;fmt&quot;&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;log&quot;&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;net/http&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ResponseWriter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fprintf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Hi there, I love %s!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HandleFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ListenAndServe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;:8080&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;do-not-use-the-root-user-at-container-runtime&quot;&gt;&lt;strong&gt;Do not use the root user at container runtime&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;This is not at all hard to elaborate, since ages even with traditional computing, running services as root was and still is exceptionally bad. Security has improved vastly since, and container technologies offer isolation through kernel namespaces, in which processes running on the container are unbeknownst from processes running on the host system. Kernel namespaces have been around since July 2008, meaning that they’ve had quite a few improvements and iterations, but nothing justifies running the application as the root user.&lt;/p&gt;

&lt;p&gt;I will point out a few examples of why this is bad.&lt;/p&gt;

&lt;h4 id=&quot;the-attack-surface-is-vastly-enlarged&quot;&gt;&lt;em&gt;The attack surface is vastly enlarged&lt;/em&gt;&lt;/h4&gt;

&lt;p&gt;The root user can do much more harm in the unlikely event of breaking out of the system during a breach. Depending on the end goal of the malicious party it can range from deletion of assets to blackmail, extortion, identity theft, invasion of privacy, fraud, part of a DDoS botnet and more.&lt;/p&gt;

&lt;h4 id=&quot;additional-applications-can-be-installed-on-the-running-container&quot;&gt;&lt;em&gt;Additional applications can be installed on the running container&lt;/em&gt;&lt;/h4&gt;

&lt;p&gt;Imagine we are running a container as the root user, and unfortunately, the application had an old third-party module that was unpatched. The attacker was able to exploit it, and has gained root access within the container. Kernel namespaces have us protected with isolation, nevertheless, the attacker can now install anything on the running container. E.g. a network scanning tool like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nmap&lt;/code&gt; which can potentially map out the network and thereby giving our imaginary vile agent a wealth of valuable information which can be used against our favor.&lt;/p&gt;

&lt;h4 id=&quot;the-root-user-can-delete-any-file&quot;&gt;&lt;em&gt;The root user can delete any file&lt;/em&gt;&lt;/h4&gt;

&lt;p&gt;If we have a container that needs to persist a state on a mounted volume, in the case of a breach all the files can be deleted from the container because there are no constraints for a user with UID and GID 0.&lt;/p&gt;

&lt;h4 id=&quot;remediation&quot;&gt;&lt;strong&gt;&lt;em&gt;Remediation&lt;/em&gt;&lt;/strong&gt;&lt;/h4&gt;

&lt;h4 id=&quot;specify-a-dedicated-user-in-docker&quot;&gt;&lt;em&gt;Specify a dedicated user in Docker&lt;/em&gt;&lt;/h4&gt;

&lt;p&gt;We should create and have a dedicated user that will run the application during container runtime:&lt;/p&gt;

&lt;div class=&quot;language-docker highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; USERNAME=&quot;webserver&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;adduser &lt;span class=&quot;nt&quot;&gt;-HD&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; /bin/noshell &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USERNAME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USERNAME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; ${USERNAME}:${USERNAME}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Full example:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Make sure you have the golang webserver sample in the same directory as the following dockerfile&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-docker highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; golang:alpine&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# All of these are arguments so that they can be overridden per use case basis.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; USERNAME=&quot;webserver&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; WORKDIR=&quot;/go/src/app&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; GOCACHE=&quot;/tmp/go_cache&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# -s SHELL  Login shell&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# -D        Don't assign a password&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# -H        Don't create home directory&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;adduser &lt;span class=&quot;nt&quot;&gt;-HD&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; /bin/noshell &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USERNAME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USERNAME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; ${WORKDIR}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; ${USERNAME}:${USERNAME}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GOCACHE&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; . .&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;go get &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    go &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; .

&lt;span class=&quot;k&quot;&gt;CMD&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; [&quot;app]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now lets build and start the webserver.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker build &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; golang_webserver &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker run &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; golang_webserver &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 8080:8080 golang_webserver
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Spawning a shell inside the running container&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker &lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; golang_webserver sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;and checking the running processes gives us the following output:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/go/src/app &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ps aux
PID   USER     TIME  COMMAND
    1 webserve  0:00 app
   26 webserve  0:00 sh
   31 webserve  0:00 ps aux
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ ps&lt;/code&gt; output clearly shows that the simple golang web server is running as a non-root user that we have previosly created and specified.&lt;/p&gt;

&lt;h3 id=&quot;be-vary-when-mounting-the-docker-socket&quot;&gt;&lt;strong&gt;Be vary when mounting the docker socket&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;Many examples on the internet casually mention that the docker socket should be mounted inside the container in particular installation instructions, which I find very alarming considering this is the main entry point for the docker API. The container that has access to the docker socket &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/run/docker.sock&lt;/code&gt; can control all the running containers on the host and as an added bonus the host is most probably compromised because the Docker daemon runs as root by default.&lt;/p&gt;

&lt;p&gt;We can see that by querying the docker socket and getting the list of images on our system(a running docker daemon, curl and jq are required).&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;--unix-socket&lt;/span&gt; /var/run/docker.sock http://localhost/images/json | jq
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will give us all the images that are present on the host where the docker daemon is running currently.&lt;/p&gt;

&lt;h3 id=&quot;do-not-run-containers-with-full-container-capabilities-also-known-as-the-privileged-flag&quot;&gt;&lt;strong&gt;Do not run containers with full container capabilities also known as the privileged flag&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;Running a container with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--privileged&lt;/code&gt; flag gives all capabilities to it plus it lifts all the limitations enforced by the cgroup controller. E.g. the container can now read the host’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/proc&lt;/code&gt; folder. It has “super capabilities” that will allow it to control the host’s devices, processes and kernel parameters. In combination with the container process running as root, the damage can be disastrous.&lt;/p&gt;

&lt;h3 id=&quot;do-not-pass-secrets-into-arguments-or-enviroment-variables-during-build-time&quot;&gt;&lt;strong&gt;Do not pass secrets into arguments or enviroment variables during build time&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;There is a common misconception/anti-pattern regarding the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ARG&lt;/code&gt; parameter within Dockerfiles, at least I have made the grave mistake thinking that passing a secret with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ARG&lt;/code&gt; parameter won’t be persisted to the final image. The Docker documentation somewhat implies that when using arguments they will be present only during build time.&lt;/p&gt;

&lt;p&gt;The following excerpt is taken from &lt;a href=&quot;https://docs.docker.com/engine/reference/commandline/build/&quot;&gt;https://docs.docker.com/engine/reference/commandline/build/&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Set build-time variables (–build-arg)&lt;br /&gt;
You can use ENV instructions in a Dockerfile to define variable values. These values persist in the built image. However, often persistence is not what you want. Users want to specify variables differently depending on which host they build an image on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ENV&lt;/code&gt; parameter it is very clear that it will be persisted in the final image. Yet it turns out that for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ARG&lt;/code&gt; the behavior is identical.&lt;/p&gt;

&lt;p&gt;Consider the following example:&lt;/p&gt;

&lt;div class=&quot;language-docker highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; golang:alpine&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; USERNAME=&quot;webserver&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; WORKDIR=&quot;/go/src/app&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; GOCACHE=&quot;/tmp/go_cache&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; API_SECRET_KEY&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# -s SHELL Login shell&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# -D       Don't assign a password&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# -H       Don't create home directory&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;adduser &lt;span class=&quot;nt&quot;&gt;-HD&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; /bin/noshell &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USERNAME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USERNAME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; ${WORKDIR}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; ${USERNAME}:${USERNAME}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; . .&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;go get &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;
    go install -v .
&lt;span class=&quot;k&quot;&gt;CMD&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; [&quot;app&quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What happens here is that we are passing an argument in the container build stage, which we would think it will only be present in the build stage. Unfortunately it will be persisted, and packed in the image.&lt;/p&gt;

&lt;p&gt;Let’s reproduce this behavior.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker build &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; build_arg_example &lt;span class=&quot;nt&quot;&gt;--build-arg&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;API_SECRET_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;VERY_SECRET_KEY&quot;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Checking the layers of the freshly built image we can see that the “very secret key” is present on the second line from the output.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker &lt;span class=&quot;nb&quot;&gt;history &lt;/span&gt;build_arg_example:latest
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
fd4fb8c4d4d9        31 seconds ago      /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#(nop)  CMD [&quot;app&quot;]                  0B&lt;/span&gt;
b59927c7a0ea        32 seconds ago      |4 &lt;span class=&quot;nv&quot;&gt;API_SECRET_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;VERY_SECRET_KEY &lt;span class=&quot;nv&quot;&gt;GOCACHE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/t…   7.46MB
afd082c6f60d        34 seconds ago      /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#(nop) COPY dir:2511c5768dd12dce0…   668B&lt;/span&gt;
6af096231a1e        36 seconds ago      /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#(nop)  USER webserver:webserver     0B&lt;/span&gt;
1560adadd916        37 seconds ago      /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#(nop) WORKDIR /go/src/app           0B&lt;/span&gt;
7ccef7155352        38 seconds ago      |4 &lt;span class=&quot;nv&quot;&gt;API_SECRET_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;VERY_SECRET_KEY &lt;span class=&quot;nv&quot;&gt;GOCACHE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/t…   4.85kB
d05b1045eb6b        39 seconds ago      /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#(nop)  ARG API_SECRET_KEY           0B&lt;/span&gt;
1ef26b6f7772        3 days ago          /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#(nop)  ARG GOCACHE=/tmp/go_cache    0B&lt;/span&gt;
a09dbe8447f7        3 days ago          /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#(nop)  ARG WORKDIR=/go/src/app      0B&lt;/span&gt;
5ba770850e29        3 days ago          /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#(nop)  ARG USERNAME=webserver       0B&lt;/span&gt;
69cf534c966a        2 weeks ago         /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#(nop) WORKDIR /go                   0B&lt;/span&gt;
&amp;lt;missing&amp;gt;           2 weeks ago         /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$GOPATH&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/src&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$GOPATH&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/b…   0B
&amp;lt;missing&amp;gt;           2 weeks ago         /bin/sh -c #(nop)  ENV PATH=/go/bin:/usr/loc…   0B
&amp;lt;missing&amp;gt;           2 weeks ago         /bin/sh -c #(nop)  ENV GOPATH=/go               0B
&amp;lt;missing&amp;gt;           2 weeks ago         /bin/sh -c set -eux;  apk add --no-cache --v…   353MB
&amp;lt;missing&amp;gt;           2 weeks ago         /bin/sh -c #(nop)  ENV GOLANG_VERSION=1.13.5    0B
&amp;lt;missing&amp;gt;           2 months ago        /bin/sh -c [ ! -e /etc/nsswitch.conf ] &amp;amp;&amp;amp; ec…   17B
&amp;lt;missing&amp;gt;           2 months ago        /bin/sh -c apk add --no-cache   ca-certifica…   551kB
&amp;lt;missing&amp;gt;           2 months ago        /bin/sh -c #(nop)  CMD [&quot;&lt;/span&gt;/bin/sh&lt;span class=&quot;s2&quot;&gt;&quot;]              0B
&amp;lt;missing&amp;gt;           2 months ago        /bin/sh -c #(nop) ADD file:fe1f09249227e2da2…   5.55MB
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;remediation-1&quot;&gt;&lt;strong&gt;&lt;em&gt;Remediation&lt;/em&gt;&lt;/strong&gt;&lt;/h4&gt;

&lt;h4 id=&quot;buildkit-multi-stage-builds&quot;&gt;&lt;em&gt;Buildkit, Multi Stage Builds&lt;/em&gt;&lt;/h4&gt;

&lt;p&gt;There are multiple methods on how can we remove secrets from the docker image during the build. I will only describe the ones that I have trialed and have been using for a while in a production-ready setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Buildkit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the latest iteration of the docker build process. According to docker, it is the “&lt;a href=&quot;https://docs.docker.com/develop/develop-images/build_enhancements/&quot;&gt;much-needed overhaul of the build architecture&lt;/a&gt;”. I prefer this method the most because it truly is an enhacement over the traditional docker build.&lt;/p&gt;

&lt;p&gt;Enabling it requires a few extra steps.&lt;/p&gt;

&lt;p&gt;First we need to create a file that will hold our “very secret key”:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'VERY_SECRET_KEY'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; very_secret_key.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then we need to overide the default docker frontend (notice the commented line at the beggining), also expose the secret inside the Dockerfile:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# syntax = docker/dockerfile:1.0-experimental&lt;/span&gt;
FROM alpine
RUN &lt;span class=&quot;nt&quot;&gt;--mount&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;secret,id&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;very_secret_key &lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; /run/secrets/very_secret_key
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Lastly we need to build the imagel and mount the secret during the build process:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ DOCKER_BUILDKIT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 docker build &lt;span class=&quot;nt&quot;&gt;--no-cache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--progress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;plain &lt;span class=&quot;nt&quot;&gt;--secret&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;very_secret_key,src&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;very_secret_key.txt &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Buildkit offers many other improvements which are out of the scope for now, head on to &lt;a href=&quot;https://docs.docker.com/develop/develop-images/build_enhancements/&quot;&gt;https://docs.docker.com/develop/develop-images/build_enhancements/&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi Stage Builds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each instruction in the Dockerfile adds a layer to the image, and all of the artifacts, arguments and environment variables are present in the final image along with the aforementioned layers. With multi-stage build, we can selectively copy artifacts and erase everything else including sensitive data. This way we can have final images that are small, secure and do not contain any delicate information.&lt;/p&gt;

&lt;p&gt;Consider the following example:&lt;/p&gt;

&lt;div class=&quot;language-docker highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 1st stage&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; golang:alpine as builder&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; USERNAME=&quot;webserver&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; WORKDIR=&quot;/go/src/app&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; GOCACHE=&quot;/tmp/go_cache&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; API_SECRET_KEY&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# -s SHELL Login shell&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# -D       Don't assign a password&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# -H       Don't create home directory&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;adduser &lt;span class=&quot;nt&quot;&gt;-HD&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; /bin/noshell &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USERNAME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USERNAME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; ${WORKDIR}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; ${USERNAME}:${USERNAME}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; . .&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;go get &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    go &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; .

&lt;span class=&quot;c&quot;&gt;# 2nd stage&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; alpine:latest&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; WORKDIR=&quot;/app&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; ${WORKDIR}&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Copy the executable from the first stage.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; --from=builder /go/bin/app /usr/local/bin&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;CMD&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; [&quot;app&quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What I’ve done here, is I got already built binary from the former image labeled as “builder”, to the latter, which doesn’t contain any of the artifacts nor environment variables. We can confirm this easily by building the multi-stage dockerfile and seeing the image history:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker build &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; multi_stage  &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker &lt;span class=&quot;nb&quot;&gt;history &lt;/span&gt;multi_stage:latest
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
d9ffea8de0f2        19 minutes ago      /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#(nop)  CMD [&quot;app&quot;]                  0B&lt;/span&gt;
a303bc8586b3        19 minutes ago      /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#(nop) COPY file:99e2ea856b1ad652…   7.45MB&lt;/span&gt;
dbe76a8816f2        21 minutes ago      /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#(nop) WORKDIR /app                  0B&lt;/span&gt;
96125fcf5377        21 minutes ago      /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#(nop)  ARG WORKDIR=/app             0B&lt;/span&gt;
965ea09ff2eb        2 months ago        /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#(nop)  CMD [&quot;/bin/sh&quot;]              0B&lt;/span&gt;
&amp;lt;missing&amp;gt;           2 months ago        /bin/sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#(nop) ADD file:fe1f09249227e2da2…   5.55MB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;image-vulnerability-scanner&quot;&gt;&lt;strong&gt;Image vulnerability scanner&lt;/strong&gt;&lt;/h4&gt;

&lt;p&gt;It is always a good practice to check the container that is running the app for any known vulnerabilities. At present, the major cloud providers provide this ability out of the box. An image vulnerability scanner works by scanning the container images in a docker repository and reports on any found and known vulnerabilities. This can be incorporated in a CI/CD pipeline with ease. One such tool is &lt;a href=&quot;https://github.com/quay/clair&quot;&gt;clair&lt;/a&gt;.&lt;/p&gt;

&lt;h4 id=&quot;slim-or-container-optimized-images&quot;&gt;&lt;strong&gt;Slim or container optimized images&lt;/strong&gt;&lt;/h4&gt;

&lt;p&gt;Small container images contain fewer packages, fewer packages means reduced attack surface. The current and most excellent container distribution is without a doubt &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;Alpine Linux&lt;/a&gt;. Most of the ready to use docker images currently offer an alpine Linux variation.&lt;/p&gt;

&lt;h4 id=&quot;use-official-container-images-only&quot;&gt;&lt;strong&gt;Use official container images only&lt;/strong&gt;&lt;/h4&gt;

&lt;p&gt;Needless to say, if the image we intend to use is provided by a third-party we do not have any way to know what might be installed on it, coupled with a mounted filesystem or &lt;a href=&quot;#be-vary-when-mounting-the-docker-socket&quot;&gt;docker socket&lt;/a&gt; the potential for harm ascends.&lt;/p&gt;

&lt;h4 id=&quot;conclusion&quot;&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;There’s no all-in-one solution for being secure, what it takes is persistence, discipline, and above all cybersecurity culture!&lt;/p&gt;

&lt;p&gt;If you enjoyed this blog post, or you found it helpful I’d be very grateful if you’d help by sharing it.&lt;/p&gt;

&lt;p&gt;Over and out.&lt;/p&gt;</content><author><name>Nikola</name></author><category term="docker" /><category term="security-practices" /><category term="security" /><category term="cyber-security" /><category term="containers-security" /><category term="docker-image" /><category term="image" /><category term="containers" /><category term="namespaces" /><category term="cgroups" /><summary type="html">It takes 20 years to build a reputation and few minutes of cyber-incident to ruin it.</summary></entry><entry><title type="html">Multi-Region Kubernetes - EKS and Terraform 0.12</title><link href="https://cloudlad.io/multi-region-kubernetes-EKS-and-terraform-012" rel="alternate" type="text/html" title="Multi-Region Kubernetes - EKS and Terraform 0.12" /><published>2019-09-10T00:00:00+00:00</published><updated>2019-09-10T00:00:00+00:00</updated><id>https://cloudlad.io/multi-region-kubernetes-EKS-and-terraform-012</id><content type="html" xml:base="https://cloudlad.io/multi-region-kubernetes-EKS-and-terraform-012">&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;a&gt;&lt;img alt=&quot;Pixel_art_background&quot; title=&quot;Pixel art background&quot; src=&quot;/assets/images/2019_09_pixel_art_background.png&quot; /&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Any technology you adopt today is a technology you’re going to have to troubleshoot tomorrow. - Anonymous&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h4 id=&quot;but-why-eks-and-kubernetes-at-all&quot;&gt;But why EKS and Kubernetes at all?&lt;/h4&gt;

&lt;p&gt;Kubernetes is the hot stuff these days. Without a doubt, some of the benefits that it offers are impressive. It is a portable, extensible, open-source platform for managing containerized applications with declarative configuration and automation.&lt;/p&gt;

&lt;p&gt;The following list is only a small subset of the features it provides:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Scaling&lt;/li&gt;
  &lt;li&gt;Deployment strategies&lt;/li&gt;
  &lt;li&gt;Container balancing&lt;/li&gt;
  &lt;li&gt;Service Discovery&lt;/li&gt;
  &lt;li&gt;Resource monitoring and logging&lt;/li&gt;
  &lt;li&gt;Self-healing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It all looks appealing and very promising, but the ultimate question is, do we really need it?&lt;/p&gt;

&lt;p&gt;Apart from the obvious benefit of having an all-in-one standardized solution that we can run our containers on, it is quite difficult to say. Unfortunately for most of the companies, it is probably not necessary. Of course, there is always the minority, companies that are required to operate under strict compliance policies, like having their hardware in-house in multiple data centers. Besides that, the times where we run one (or a few) applications per server are long gone. The intricacy of working in a team especially a large one supplements the difficulty, which by itself is one of the hardest hurdles to overcome, and we all know that that there is no perfect, out-of-the-box solution.&lt;/p&gt;

&lt;p&gt;Having said that, running and managing a Kubernetes cluster can be a daunting job, but as with the other tech tools at hand, combined with the strive in finding the “sweet spot” between outsourcing them and creating or maintaining the smallest possible subset of them, Kubernetes ultimately improves stability, reliability, speed, and costs.&lt;/p&gt;

&lt;p&gt;So let us explore how easy is to get a Multi-Region EKS cluster setup up and running.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;The judgment of sacrificing either speed or stability is the bane of the engineering world.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;br /&gt;
The Amazon Elastic Container Service for Kubernetes tries to ease some of the sophistication that comes with Kubernetes with it’s offering, by extracting and managing one of the most important components of it, the &lt;a href=&quot;https://kubernetes.io/docs/concepts/#kubernetes-control-plane&quot;&gt;Control Plane&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It has the following additional features:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;High availability&lt;/li&gt;
  &lt;li&gt;Control Plane logging to AWS CloudWatch&lt;/li&gt;
  &lt;li&gt;Managed updates&lt;/li&gt;
  &lt;li&gt;Backup&lt;/li&gt;
  &lt;li&gt;Integration with other AWS services&lt;/li&gt;
  &lt;li&gt;Service mesh&lt;/li&gt;
  &lt;li&gt;Fine-Grained IAM Roles for Service Accounts&lt;/li&gt;
  &lt;li&gt;Container Storage Interface (CSI)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;One of the finest features of EKS is that it is a &lt;a href=&quot;https://aws.amazon.com/eks/features/#Certified_conformant&quot;&gt;certified Kubernetes conformant&lt;/a&gt;, meaning that the applications running on it are fully compatible with any standard Kubernetes environment and vice versa.&lt;/p&gt;

&lt;p&gt;To determine whether EKS is the right tool for the job we would need to get it working.
For this, I will demonstrate how we can run a Kubernetes cluster on AWS EKS in two separate regions by using the latest iteration of Terraform as our Infrastructure as Code/Software tool.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
First let us see how AWS has managed to implement Kubernetes, with a simplified overview of its architecture. The EKS API is exposed via a Network Load Balancer on which we can orchestrate the cluster with kubectl and where the worker nodes connect to. Communication between the control plane and the worker nodes is done through an ENI interface.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;a&gt;&lt;img alt=&quot;EKS_overview&quot; title=&quot;EKS overview&quot; src=&quot;/assets/images/2019_09_eks_overview.png&quot; /&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
Before we begin with the technicalities some considerations need to be taken into account:&lt;/p&gt;

&lt;p&gt;DNS&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The VPC needs to have DNS hostname and DNS resolution support enabled.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;IAM&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If we would like to have some of the resources in Kubernetes exposed via Route53 with a tool like &lt;a href=&quot;https://github.com/kubernetes-incubator/external-dns&quot;&gt;ExternalDNS&lt;/a&gt; then we shouldn’t forget about setting the correct IAM permissions (they are out of scope for this blog post).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Networking&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Private-only: Everything runs in a private subnet and Kubernetes cannot create internet-facing load balancers for your pods.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Public-only: Everything runs in a public subnet, including your worker nodes.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tagging Requirements&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The following tags should be set on the respective resources so that Kubernetes can discover them.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Subnet&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubernetes.io/cluster/&amp;lt;cluster-name&amp;gt; = shared&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;VPC&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubernetes.io/cluster/&amp;lt;cluster-name&amp;gt; = shared&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Worker Nodes&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubernetes.io/cluster/&amp;lt;cluster-name&amp;gt; = owned&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Terraform&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Please make sure you have the &lt;a href=&quot;https://www.terraform.io/downloads.html&quot;&gt;latest version of terraform&lt;/a&gt; installed on your system or any version bigger than 0.12.&lt;/li&gt;
  &lt;li&gt;The terraform examples &lt;a href=&quot;https://github.com/parabolic/examples/tree/master/terraform/eks&quot;&gt;can be found here&lt;/a&gt;, which will be needed for the creation of the clusters and the accompanying resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Now that we have that out of the way and we have gained an overview on how AWS has put Kubernetes into operation, we can safely head on to creating two clusters in two separate regions.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;a&gt;&lt;img alt=&quot;EKS_multi_region&quot; title=&quot;EKS Multi Region&quot; src=&quot;/assets/images/2019_09_eks_multi_region.png&quot; /&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h4 id=&quot;time-for-some-fun-with-terraform&quot;&gt;Time for some fun with terraform.&lt;/h4&gt;

&lt;p&gt;I will be using &lt;a href=&quot;https://www.terraform.io/docs/state/workspaces.html&quot;&gt;terraform workspaces&lt;/a&gt; with which I can conveniently use single set of templates within a single backend and with that I can create clusters in as many regions as I want just by using one single configuration set.&lt;/p&gt;

&lt;p&gt;By creating a local variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;local.region&lt;/code&gt; I have implemented a simple check that detects whether the workspace is default one, which will subsequently stop terraform plan/apply. Setting a bogus region will render this check useless but plan/apply will fail nevertheless.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;locals {
  region = terraform.workspace == &quot;default&quot; ? &quot;!!!Please set the terraform workspace to a valid region!!!&quot; : terraform.workspace
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Then in turn I am using the local variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;local.region&lt;/code&gt; in the aws provider configuration.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;provider &quot;aws&quot; {
  version = &quot;~&amp;gt; 2.27.0&quot;

  profile = local.profile
  region  = local.region
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;
Now, before jumping on creating the clusters, we need to make sure we have the desired workspaces created since they will represent the regions. If we do not have them set, we should do it now.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I will be operating in the regions “eu-west-1” and “eu-west-2”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The terraform commands should be executed in the folder where the terraform templates are&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;terraform workspace new eu-west-1
Created and switched to workspace &lt;span class=&quot;s2&quot;&gt;&quot;eu-west-1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;

You&lt;span class=&quot;s1&quot;&gt;'re now on a new, empty workspace. Workspaces isolate their state,
so if you run &quot;terraform plan&quot; Terraform will not see any existing state
for this configuration.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;
After we have the workspaces created we can list them and verify which one we are working on (denoted with an asterisk).&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;terraform workspace list
  default
&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; eu-west-1
  eu-west-2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;
With the  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;workspace select&lt;/code&gt; we can conveniently switch between workspaces.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;terraform workspace &lt;span class=&quot;k&quot;&gt;select &lt;/span&gt;eu-west-2
Switched to workspace &lt;span class=&quot;s2&quot;&gt;&quot;eu-west-2&quot;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;
With terraform workspaces we are using the same set of templates without duplication and we are sort of “branching out” by changing the workspace which is considered as separate terraform state, consequently spliting the infrastructure into two separate regions.&lt;/p&gt;

&lt;p&gt;And one more thing before applying the terraform configuration, please make sure you have replaced the following local variables so that they represent your local setup:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;locals {
  profile     = &quot;testbed&quot;
  allowed_ips = [&quot;0.0.0.0/0&quot;]
  key_name    = &quot;&quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;
We can now finally apply terraform, by running:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Initially, I wanted to include the details for &lt;a href=&quot;https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html&quot;&gt;configuring kubectl&lt;/a&gt;, but because it is a lot of information and there already is more than enough, I have decided that it should stay out of the scope for this blog post. From this point on I will assume that &lt;a href=&quot;https://docs.aws.amazon.com/eks/latest/userguide/create-kubeconfig.html&quot;&gt;kubectl is set up&lt;/a&gt; and working properly.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform apply&lt;/code&gt; has finished we will see the resources created in the AWS console, but there is still some work left to connect the workers to the EKS control plane. What we can do at this point is, to validate if the cluster and kubectl are working as intended, and the simplest confirmation that we can do is, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl cluster-info&lt;/code&gt; which should give us output similar to this.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl cluster-info
Kubernetes master is running at https://5E80D2AFE1D478990138A7FCE4038714.yl4.eu-west-1.eks.amazonaws.com
CoreDNS is running at https://5E80D2AFE1D478990138A7FCE4038714.yl4.eu-west-1.eks.amazonaws.com/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use &lt;span class=&quot;s1&quot;&gt;'kubectl cluster-info dump'&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;
Unfortunately, we are not there, yet.&lt;/p&gt;

&lt;p&gt;The EKS service does not provide a cluster-level API parameter or resource to automatically configure the workers to join the EKS control plane via AWS IAM authentication, so we would need to get the ConfigMaps from terraform’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config_map_aws_auth&lt;/code&gt; output and apply it with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl apply -f&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That output will contain the following yaml data:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please note that in the place of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;AWS_ACCOUNT_ID&amp;gt;&lt;/code&gt; you should see your current AWS account id&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ConfigMap&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;aws-auth&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;kube-system&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;mapRoles&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;- rolearn: arn:aws:iam::&amp;lt;AWS_ACCOUNT_ID&amp;gt;:role/eks-worker-eu-west-1-test-cloudlad&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;username: system:node:{{EC2PrivateDNSName}}&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;groups:&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;- system:bootstrappers&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;- system:nodes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;After applying the ConfigMap we will see the nodes connected to the EKS master.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl apply &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; config_map_aws_auth.yaml
configmap/aws-auth created
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;
We can confirm with the command &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl get nodes&lt;/code&gt; that the worker nodes are connected to the EKS master.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl get nodes
NAME                                        STATUS   ROLES    AGE   VERSION
ip-10-0-102-59.eu-west-1.compute.internal   Ready    &amp;lt;none&amp;gt;   26s   v1.13.8-eks-cd3eb0
ip-10-0-103-79.eu-west-1.compute.internal   Ready    &amp;lt;none&amp;gt;   25s   v1.13.8-eks-cd3eb0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;
For the sake of completeness here is an example of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cluster-info&lt;/code&gt; and the nodes connected to the EKS cluster in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eu-west-2&lt;/code&gt; region&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl cluster-info
Kubernetes master is running at https://D89FEC781B28CA9338FC8C2A13006773.sk1.eu-west-2.eks.amazonaws.com
CoreDNS is running at https://D89FEC781B28CA9338FC8C2A13006773.sk1.eu-west-2.eks.amazonaws.com/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use &lt;span class=&quot;s1&quot;&gt;'kubectl cluster-info dump'&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;kubectl get nodes
NAME                                         STATUS     ROLES    AGE   VERSION
ip-10-0-102-88.eu-west-2.compute.internal    NotReady   &amp;lt;none&amp;gt;   4s    v1.13.8-eks-cd3eb0
ip-10-0-103-232.eu-west-2.compute.internal   NotReady   &amp;lt;none&amp;gt;   10s   v1.13.8-eks-cd3eb0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;In a nutshell, we have successfully created two identical EKS clusters in two separate regions, and we were able to connect the worker nodes with the control plane and to verify that everything is functioning well. In the current state, we are ready to take the next steps e.g. deploying a sample app on Kubernetes but that alone deserves a separate blog post.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
If you find this blog post useful and interesting please spread the word by sharing. For any suggestions and proposals feel free to contact me. Thank you for reading!&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
For those interested in some more details about the terraform implementation please read on.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 id=&quot;terraform-configuration-details&quot;&gt;Terraform configuration details&lt;/h4&gt;

&lt;p&gt;VPC&lt;/p&gt;

&lt;p&gt;For the creation of the VPC, I have settled on using the offical terraform module &lt;a href=&quot;https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/2.12.0&quot;&gt;https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/2.12.0&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;module &quot;vpc&quot; {
  source  = &quot;terraform-aws-modules/vpc/aws&quot;
  version = &quot;2.12.0&quot;

  name = module.label.id
  cidr = local.vpc_cidr

  azs             = [&quot;${local.region}a&quot;, &quot;${local.region}b&quot;, &quot;${local.region}c&quot;]
  private_subnets = local.private_subnets_cidrs
  public_subnets  = local.public_subnets_cidrs

  enable_nat_gateway = true
  enable_vpn_gateway = true

  # For the EKS workers.
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = module.label_shared.tags
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Tags&lt;/p&gt;

&lt;p&gt;The real challenge was how to set the tags in a nonrepetitive and useful manner. I’ve used cloudposse’s &lt;a href=&quot;https://github.com/cloudposse/terraform-null-label&quot;&gt;terraform-null-label&lt;/a&gt; which can call itself (works with terraform 0.12 and up only!) and construct a hash of tags for the shared or owned notation for the EKS requirements, and as stated above, the VPC and the subnets will obtain the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shared&lt;/code&gt; tag and the workers the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;owned&lt;/code&gt; tag.&lt;/p&gt;

&lt;p&gt;The following example only works with terraform 0.12 and above.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;module &quot;label_owned&quot; {
  source    = &quot;git::https://github.com/cloudposse/terraform-null-label.git?ref=0.15.0&quot;
  stage     = local.stage
  name      = local.name
  namespace = local.region
  delimiter = &quot;-&quot;
  tags = {
    &quot;kubernetes.io/cluster/${module.label_owned.id}&quot; = &quot;owned&quot;
  }
  additional_tag_map = {
    propagate_at_launch = &quot;true&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The parameter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.label_owned.id&lt;/code&gt; is being used as the EKS cluster name which enables me to call the module within itself &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;kubernetes.io/cluster/${module.label_owned.id}&quot; = &quot;owned&quot;&lt;/code&gt; therefore removing the need for repetition.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
ConfigMap&lt;/p&gt;

&lt;p&gt;The ConfigMap that we are applying with kubectl is a local variable (see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;locals.tf&lt;/code&gt;) that has the parameter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rolearn:&lt;/code&gt; computed and interpolated, the rest is written in a standard yaml syntax. It is shown on stdout with terraform outputs (see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;outputs.tf&lt;/code&gt;).&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;locals {
  config_map_aws_auth = &amp;lt;&amp;lt;CONFIGMAPAWSAUTH
apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    - rolearn: ${aws_iam_role.eks_worker.arn}
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes
CONFIGMAPAWSAUTH
}

output &quot;config_map_aws_auth&quot; {
  value = local.config_map_aws_auth
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Nikola</name></author><category term="kubernetes" /><category term="k8s" /><category term="terraform" /><category term="0.12" /><category term="AWS" /><category term="EKS" /><category term="multi-region" /><category term="IaC" /><category term="infrastructure-as-code" /><summary type="html">Any technology you adopt today is a technology you're going to have to troubleshoot tomorrow.</summary></entry><entry><title type="html">Cloudwatch agent as a terraform module</title><link href="https://cloudlad.io/cloudwatch-agent-as-a-terraform-module" rel="alternate" type="text/html" title="Cloudwatch agent as a terraform module" /><published>2019-05-19T00:00:00+00:00</published><updated>2019-05-19T00:00:00+00:00</updated><id>https://cloudlad.io/cloudwatch-agent-as-a-terraform-module</id><content type="html" xml:base="https://cloudlad.io/cloudwatch-agent-as-a-terraform-module">&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;watching_clouds&quot; title=&quot;Watching Clouds&quot; src=&quot;/assets/images/2019_05_19_watching_clouds.png&quot; style=&quot;width:1000px;height:400px;&quot; align=&quot;middle&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
&lt;strong&gt;TL;DR: &lt;a href=&quot;https://github.com/parabolic/examples/tree/master/terraform/ec2_instance_cloudwatch_agent&quot;&gt;I do not have time for reading this please take me to the examples&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
Let me kick start this blog post with the notation of a reusable pattern in engineering, which reminds me of a particularly amazing book that I have read recently. I find the following excerpt exceptional:&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;‘The purpose of a pattern is to provide general advice or structure to guide your design.’&lt;/p&gt;
  &lt;h6 id=&quot;designing-distributed-systems-patterns-and-paradigms-for-scalable-reliable-services-by-brendan-burns-2018&quot;&gt;&lt;a href=&quot;https://www.oreilly.com/library/view/designing-distributed-systems/9781491983638/&quot;&gt;Designing Distributed Systems: Patterns and Paradigms for Scalable, Reliable Services&lt;/a&gt;, by Brendan Burns, 2018&lt;/h6&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;br /&gt;
How I understand it which is also described further in the book, when designing distributed systems, instead of learning by failing (though inevitable most of the times) we can use pre-defined and reusable patterns that will help us in creating a robust and stable architecture. This doesn’t mean that the same approach works exclusively in IT. As an example try cooking a meal without a recipe. Being an enthusiast and practitioner of Infrastructure as code/software, I will be showcasing this paradigm with terraform modules.&lt;/p&gt;

&lt;p&gt;Putting the big picture aside I am going to focus on one small bit, server monitoring on AWS’s EC2 service. One of the first ones that got created back in 2006 when the IaaS model was still in its infancy.&lt;/p&gt;

&lt;p&gt;If you have worked with AWS before or you are still using it, it is very likely you have used the EC2 web console to spin up an instance. After requesting and getting an instance you will notice that EC2 provides server monitoring for the running instance(s) but it is by default, maybe by design as well, very basic.&lt;/p&gt;

&lt;p&gt;To overcome the problem with the rudimentary monitoring we usually fall back to employing third-party services that give us more complete and comprehensive insights. Most of the times they do the job very well, but as usual, there are some drawbacks.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Familiarity.&lt;/strong&gt; We have to leave the the AWS web console and venture into the unknown third-party GUIs which are generally satisfactory, yet as a user that is habituated to the former, it can feel a bit disorderly. A lot of companies use more than one server monitoring provider and that is the moment when it becomes very trying.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;User management.&lt;/strong&gt; When using a third-party service for server monitoring we need to somehow manage the users. I am not speaking about whether they offer SAML integration or not, but about the hindrance of managing users and Role-based access control.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Alerting.&lt;/strong&gt; It is much easier to create alarms in CloudWatch where both the API and the features are very complete.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Infrastructure as Code/Software.&lt;/strong&gt; The last but not the least, not all third-party server monitoring providers can be managed via an IaC tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see what I am aiming at right? CloudWatch Agent, a daemon that can collect system-level, custom metrics (using StatsD and collectd), logs both from EC2 and on-premise instances and dispatch them to CloudWatch. We can have all of the server monitoring metrics in one place and deployable as a reusable terraform module.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Install-CloudWatch-Agent.html&quot;&gt;Click here for more information about the CloudWatch Agent.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;but-what-is-a-terraform-module&quot;&gt;But what is a terraform module?&lt;/h3&gt;

&lt;p&gt;This is how hashicorp describes terraform modules.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;A module is a container for multiple resources that are used together.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;br /&gt;
Aside from that modules are reusable, versionable, testable, and callable from within other modules. They should be as small as possible to have a small blast radius in case of an error and to address the edge cases.&lt;/p&gt;

&lt;p&gt;Terraform supports various &lt;a href=&quot;https://www.terraform.io/docs/modules/sources.html&quot;&gt;source types&lt;/a&gt; for getting the source code of the modules.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;module &lt;span class=&quot;s2&quot;&gt;&quot;cloudwatch_agent&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;git::https://github.com/cloudposse/terraform-aws-cloudwatch-agent?ref=0.2.0&quot;&lt;/span&gt;

  name      &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cloudwatch_agent&quot;&lt;/span&gt;
  namespace &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;eg&quot;&lt;/span&gt;
  stage     &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dev&quot;&lt;/span&gt;

  aggregation_dimensions      &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;AutoScalingGroupName&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;InstanceId&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
  disk_resources              &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;/mnt&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;/home&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
  metrics_collection_interval &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;60&quot;&lt;/span&gt;
  metrics_config              &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;advanced&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;so-how-does-this-particular-module-work&quot;&gt;So how does this particular module work?&lt;/h3&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;mime_multipart_cloud_init&quot; title=&quot;Mime Multipart Archive&quot; src=&quot;/assets/images/2019_05_19_terraform.png&quot; align=&quot;middle&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;
The CloudWatch terraform module currently supports collecting metrics and can only be applied on AWS/EC2, however if you have an awesome idea for an improvement or are in need an additional feature, you can either make a contribution or open an issue or here: &lt;a href=&quot;https://github.com/cloudposse/terraform-aws-cloudwatch-agent/&quot;&gt;https://github.com/cloudposse/terraform-aws-cloudwatch-agent/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Packing it all together into a reusable module has its hardships. Let us start with the uncomplicated step, installing the CloudWatch Agent package by using cloud-init in order to detect the underlyin OS utilizing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/os-release&lt;/code&gt;, a &lt;a href=&quot;https://www.freedesktop.org/software/systemd/man/os-release.html&quot;&gt;systemd configuration file&lt;/a&gt;. This will download, install, and run the daemon.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;runcmd&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;. /etc/os-release&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;case $NAME in&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;&quot;Amazon Linux&quot;) echo &quot;Installing the cloudwatch agent for Amazon Linux.&quot;&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;curl -O https://s3.amazonaws.com/amazoncloudwatch-agent/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;rpm -U ./amazon-cloudwatch-agent.rpm&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;;;&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;Centos) echo &quot;Installing the cloudwatch agent for Centos Linux.&quot;&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;curl -O https://s3.amazonaws.com/amazoncloudwatch-agent/centos/amd64/latest/amazon-cloudwatch-agent.rpm&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;rpm -U ./amazon-cloudwatch-agent.rpm&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;;;&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;Debian) echo &quot;Installing the cloudwatch agent for Debian Linux.&quot;&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;curl -O https://s3.amazonaws.com/amazoncloudwatch-agent/debian/amd64/latest/amazon-cloudwatch-agent.deb&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;dpkg -i -E ./amazon-cloudwatch-agent.deb&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;;;&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;Redhat) echo &quot;Installing the cloudwatch agent for Redhat Linux.&quot;&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;curl -O https://s3.amazonaws.com/amazoncloudwatch-agent/redhat/amd64/latest/amazon-cloudwatch-agent.rpm&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;rpm -U ./amazon-cloudwatch-agent.rpm&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;;;&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;Suse) echo &quot;Installing the cloudwatch agent for Suse Linux.&quot;&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;curl -O https://s3.amazonaws.com/amazoncloudwatch-agent/suse/amd64/latest/amazon-cloudwatch-agent.rpm&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;rpm -U ./amazon-cloudwatch-agent.rpm&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;;;&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;Ubuntu) echo &quot;Installing the cloudwatch agent for Ubuntu Linux.&quot;&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;curl -O https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;dpkg -i -E ./amazon-cloudwatch-agent.deb&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;;;&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;*)&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;echo &quot;Operating system not supported. Please refer to the official documents for more info https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/install-CloudWatch-Agent-on-first-instance.html&quot;&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;esac&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/etc/cloudwatch_agent.json -s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At the very end, the command for starting the daemon references a specific configuration file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;file:/etc/cloudwatch_agent.json&lt;/code&gt;. It defines which sets of metrics and detail levels will be monitored from the OS. The AWS CloudWatch Agent has three levels of granularity detail, Basic, Standard, and Advanced. For convenience and simplicity, I have decided to use a pre-generated configuration file which has only the Standard and Advanced detail levels. I have &lt;a href=&quot;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/create-cloudwatch-agent-configuration-file-wizard.html&quot;&gt;generated the configuration file with using the Wizard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Switching between the metrics granularity is done by passing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;metrics_config&lt;/code&gt; parameter when invoking the module:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;module &lt;span class=&quot;s2&quot;&gt;&quot;cloudwatch_agent&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;git::https://github.com/cloudposse/terraform-aws-cloudwatch-agent?ref=0.2.0&quot;&lt;/span&gt;

  name      &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cloudwatch_agent&quot;&lt;/span&gt;
  namespace &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;eg&quot;&lt;/span&gt;
  stage     &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dev&quot;&lt;/span&gt;

  metrics_config &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;advanced&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Accepts only standard or advanced.&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Inside the main.tf within the module the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;metrics_config&lt;/code&gt; parameter will be used to either load the standard or the advanced configuration file with terraform conditionals.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;data &lt;span class=&quot;s2&quot;&gt;&quot;template_file&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cloud_init_cloudwatch_agent&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  template &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.module&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/templates/cloud_init.yaml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

  vars &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    cloudwatch_agent_configuration &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.metrics_config == &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;standard&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt; ? base64encode(data.template_file.cloudwatch_agent_configuration_standard.rendered) &lt;/span&gt;:&lt;span class=&quot;p&quot;&gt; base64encode(data.template_file.cloudwatch_agent_configuration_advanced.rendered)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For the EC2 instance to be able to send any metrics to CloudWatch we need to set the IAM permissions accordingly. The module exports both the role and the JSON Policy. Depending on our use case we can select between the two.&lt;/p&gt;

&lt;p&gt;Here’s an example with using the role:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;module &lt;span class=&quot;s2&quot;&gt;&quot;cloudwatch_agent&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;git::https://github.com/cloudposse/terraform-aws-cloudwatch-agent?ref=0.2.0&quot;&lt;/span&gt;

  name      &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cloudwatch_agent&quot;&lt;/span&gt;
  namespace &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;eg&quot;&lt;/span&gt;
  stage     &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dev&quot;&lt;/span&gt;

  metrics_config &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;advanced&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Accepts only standard or advanced.&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

resource &lt;span class=&quot;s2&quot;&gt;&quot;aws_iam_instance_profile&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cloudwatch_agent&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  name_prefix &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cloudwatch_agent&quot;&lt;/span&gt;
  role        &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.cloudwatch_agent.role_name&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# The exported role from the module.&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Or using the JSON policy:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;module &lt;span class=&quot;s2&quot;&gt;&quot;cloudwatch_agent&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;git::https://github.com/cloudposse/terraform-aws-cloudwatch-agent?ref=0.2.0&quot;&lt;/span&gt;

  name      &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cloudwatch_agent&quot;&lt;/span&gt;
  namespace &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;eg&quot;&lt;/span&gt;
  stage     &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dev&quot;&lt;/span&gt;

  metrics_config &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;advanced&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Accepts only standard or advanced.&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
resource &lt;span class=&quot;s2&quot;&gt;&quot;aws_iam_role_policy&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cloudwatch_agent&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  name   &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cloudwatch_agent&quot;&lt;/span&gt;
  policy &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.cloudwatch_agent.iam_policy_document&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# The exported JSON policy from the module.&lt;/span&gt;
  role   &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;aws_iam_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.ec2.id&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will get our CloudWatch agent up and running on our EC2 instance. For the observant eye, there is a serious flaw with cloud-init in the current setup. What will happen if we have already an existing cloud-init configuration? We should somehow make it work with the cloud-init configuration from the module. For this reason, the &lt;strong&gt;Mime Multi Part Archive&lt;/strong&gt; capability from cloud-init was created which in terraform is a data source called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;template_cloudinit_config&lt;/code&gt;. The module takes care of the intricate Mime Multi Part Archive. The only change that needs to be done is adding the attribute &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;userdata_part_content&lt;/code&gt; when calling the module, meaning that we are going to pass our cloud-init configuration and in return, we will get a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mime Multi Part Archive&lt;/code&gt; cloud-init which can be used in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launch_configuration&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;module &lt;span class=&quot;s2&quot;&gt;&quot;cloudwatch_agent&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;git::https://github.com/cloudposse/terraform-aws-cloudwatch-agent?ref=0.2.0&quot;&lt;/span&gt;

  name      &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cloudwatch_agent&quot;&lt;/span&gt;
  namespace &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;eg&quot;&lt;/span&gt;
  stage     &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dev&quot;&lt;/span&gt;

  metrics_config        &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;advanced&quot;&lt;/span&gt;
  userdata_part_content &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.template_file.cloud-init.rendered&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# This is where we pass the cloud-init to the module.&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

data &lt;span class=&quot;s2&quot;&gt;&quot;template_file&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cloud-init&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  template &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.module&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/cloud-init.yml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

resource &lt;span class=&quot;s2&quot;&gt;&quot;aws_launch_configuration&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;multipart&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  name_prefix          &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cloudwatch_agent&quot;&lt;/span&gt;
  image_id             &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.aws_ami.ecs-optimized.id&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  iam_instance_profile &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;aws_iam_instance_profile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.cloudwatch_agent.name&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  instance_type        &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;t2.micro&quot;&lt;/span&gt;
  user_data_base64     &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.cloudwatch_agent.user_data&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Multipart cloud-init from the module gzipped and base64encoded.&lt;/span&gt;
  security_groups      &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;aws_security_group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.ec2.id&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
  key_name             &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.ssh_key_pair&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

  lifecycle &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    create_before_destroy &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

resource &lt;span class=&quot;s2&quot;&gt;&quot;aws_cloudwatch_metric_alarm&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mem_available_percent_alert&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  alarm_description &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Available memory for &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;aws_autoscaling_group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.ecs_cluster.name&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; is below 20 percent.&quot;&lt;/span&gt;

  alarm_name &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;aws_autoscaling_group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.ecs_cluster.name&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;-mem-available-percent-alert&quot;&lt;/span&gt;

  metric_name         &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mem_available_percent&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# The metric name as reported by the CloudWatch Agent.&lt;/span&gt;
  namespace           &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;CWAgent&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# The namespace for the CloudWatch Agent.&lt;/span&gt;
  comparison_operator &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;LessThanOrEqualToThreshold&quot;&lt;/span&gt;
  evaluation_periods  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;1&quot;&lt;/span&gt;
  period              &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;60&quot;&lt;/span&gt;
  statistic           &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Minimum&quot;&lt;/span&gt;
  threshold           &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;20&quot;&lt;/span&gt;
  treat_missing_data  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;notBreaching&quot;&lt;/span&gt;

  dimensions &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    AutoScalingGroupName &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;aws_autoscaling_group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.ecs_cluster.name&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# The name of the Autoscaling Group.&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  alarm_actions &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;aws_autoscaling_policy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.scale_up.arn&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;,
  &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;
In the end we should have our metrics aggregated and shown in CloudWatch.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;cloud_watch_graph&quot; title=&quot;Cloud Watch Graph&quot; src=&quot;/assets/images/2019_05_19_cloud_watch_graph.png&quot; align=&quot;middle&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;It is worth mentioning that the module provides further tailoring via the following parameters:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aggregation_dimensions&lt;/code&gt; parameter which defaults to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[&quot;InstanceId&quot;, &quot;AutoScalingGroupName&quot;]&lt;/code&gt; will aggreggate metrics so that alerting can be done within an autoscaling group.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;metrics_collection_interval&lt;/code&gt; which defailts to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;60&lt;/code&gt;, specifies how often to collect the cpu metrics and if it’s below 60 seconds then AWS will bill those metrics as high-resolution ones.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;disk_resources&lt;/code&gt; which defaults to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt;, Specifies an array of disk mount points. This field limits CloudWatch to collect metrics from only the listed mount points. You can specify &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*&lt;/code&gt; as the value to collect metrics from all mount points.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Agent-Configuration-File-Details.html&quot;&gt;Click here for more information about all configuration parameters.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
That is all for now. If you find this blog post useful and interesting please spread the word by sharing. For any suggestions and proposals feel free to contact me.&lt;/p&gt;</content><author><name>Nikola</name></author><category term="cloudwatch" /><category term="cloudwatch-agent" /><category term="terraform" /><category term="module" /><category term="AWS" /><category term="EC2" /><category term="monitoring" /><category term="terraform-module" /><category term="server-monitoring" /><summary type="html">A module is a container for multiple resources that are used together.</summary></entry><entry><title type="html">Docker persistence for local development</title><link href="https://cloudlad.io/Docker-persistence-for-local-development" rel="alternate" type="text/html" title="Docker persistence for local development" /><published>2019-01-08T00:00:00+00:00</published><updated>2019-01-08T00:00:00+00:00</updated><id>https://cloudlad.io/docker-host-persistence-pattern</id><content type="html" xml:base="https://cloudlad.io/Docker-persistence-for-local-development">&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;docker_host_persistence_patterns&quot; title=&quot;Docker host persistence patterns&quot; src=&quot;/assets/images/2019_01_06_docker_host_persistence_patterns.png&quot; style=&quot;width:800px;height:400px;&quot; align=&quot;middle&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;containers-and-data-persistence&quot;&gt;Containers and data persistence&lt;/h3&gt;

&lt;p&gt;In this blog post we will be exploring the possibilities to manage, persist and share data between a host and a container by utilizing a simple pattern dubbed “host based data persistence”.&lt;/p&gt;

&lt;p&gt;Before I start diving into technicalities I reckon it is better to clarify a few things so that readers from all levels are on par with what I will be explaining.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
The following excerpt is taken from the docker documentation:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;By default all files created inside a container are stored on a writable container layer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;br /&gt;
But what does this actually mean? Taking into account that the docker layers reside on the host system under /var/lib/docker/{storage-driver-name}, where storage-driver-name can be any of the supported &lt;a href=&quot;https://docs.docker.com/storage/storagedriver/select-storage-driver/&quot;&gt;storage drivers&lt;/a&gt; for docker, it means that all the data that gets copied or written inside the container will not persist. At least not in the way we expect it to. Furthermore, the fact that it is stored somewhere on the host system makes it even harder to manage or migrate.
This behavior somewhat explains why we should not run applications that have a state on a production environment in docker unless we absolutely know what we are doing. But that does not stop us from doing so on a testing environment, let’s say like a local dev setup.&lt;/p&gt;

&lt;p&gt;Read on to see how I have implemented this pattern in docker.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
&lt;strong&gt;Question time!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
&lt;strong&gt;Q:&lt;/strong&gt; When a file gets created by the process that runs inside the container, which UID and GID will it get assigned?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; It will have the UID and GID of the user that is set with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;USER&lt;/code&gt; instruction in the Dockerfile or 0 (the root user) if the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;USER&lt;/code&gt; instruction is not specified.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;obstacle&quot;&gt;Obstacle&lt;/h3&gt;
&lt;p&gt;As you might have noticed by now this blog platform runs with Jekyll on GitHub. Because Jekyll is a gem and behaves like an ordinary ruby application it is the perfect candidate for running in a container. I will not bore you with all the details, but if I have piqued your interest you can have a look at the repository for the complete setup &lt;a href=&quot;https://github.com/parabolic/parabolic.github.io&quot;&gt;https://github.com/parabolic/parabolic.github.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let us assume that we already have our nice little blog dockerized and in order to see how it looks like, we need to have a look at it before sending it on its merry journey to the GitHub cloud. Jekyll has a nice feature that regenerates the site when files are modified. The argument in question is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--watch&lt;/code&gt;. That is very pleasant and helpful but with a simple docker setup where we copy the needed files upon build time inside the container, anything that we change on the host after the container is started is not going to be updated to the container layer that we wrote the data to, consequently rendering Jekyll’s regeneration feature rather useless.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;img alt=&quot;running_container_changes&quot; title=&quot;Running container changes&quot; src=&quot;/assets/images/2019_01_06_running_container_changes.png&quot; style=&quot;width:800px;height:400px;&quot; align=&quot;middle&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;This is easily solvable by mounting a volume from the root level of the project to the running container and Jekyll will be able to detect the changes done from the host, but we will soon find out that all the files that were created by the dockerized Jekyll app on the host system will be owned by the root user or any other user rather that the one that we are logged into our system with.  This is normal behavior but it makes matters very inconvenient by having to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chown&lt;/code&gt; the files back to our current system user.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
Note:&lt;/p&gt;

&lt;p&gt;The solution that I am about to explain can be used for other web applications too. If we want our changes to be conveyed to the running application inside the container immediately and with the proper UID and GID set, we should have a similar implementation.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;solution&quot;&gt;Solution&lt;/h3&gt;
&lt;p&gt;It is apparent that we would need to somehow sync the correct UID and GID between the host and the container, otherwise we would need to manually change the ownership back to our system user with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chown&lt;/code&gt; command.
In order to do so, some extra work needs to be done. I am usually running and building docker with docker-compose which will help in leveraging the final result.&lt;/p&gt;

&lt;p&gt;Firstly we need to somehow tell the container about our current UID and GID and since docker-compose will be controlling the lifecycle of the container we need to inject said UID and GID from it. It turns out that with &lt;a href=&quot;https://github.com/parabolic/parabolic.github.io/blob/master/docker-compose.yml&quot;&gt;docker-compose.yml&lt;/a&gt; you can read environment variables which will be passed to the container as arguments (can be used with environment variables as well).&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;UID=${UID}&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;GID=${GID}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;We should make sure we are passing those arguments in the Dockefile along with defining the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;USER&lt;/code&gt; instruction.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div class=&quot;language-dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; ruby:2.5.3-alpine3.8&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;LABEL&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; maintainer.url=&quot;https://github.com/parabolic&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;LABEL&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; description=&quot;A dockerized jekyll that regenerates the site on file change.&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;LABEL&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; maintainer.name=&quot;Nikola Velkovski&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; UID&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; GID&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; APP_HOME=&quot;/app&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;apk add &lt;span class=&quot;nt&quot;&gt;--update&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;    build-base

&lt;span class=&quot;k&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; ${APP_HOME}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; Gemfile* ./&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;bundle &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-j&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;getconf _NPROCESSORS_ONLN&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Run as the user with the UID and GID from the arguments.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; ${UID}:${GID}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Before running the container with docker-compose we need to set the environment variables by sourcing the following bash script &lt;a href=&quot;https://github.com/parabolic/parabolic.github.io/blob/master/docker_env&quot;&gt;docker_env&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;UID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;And finally, the current directory will be mounted to the working directory inside the container at run time, thus eliminating a layer in the Dockerfile where we copy the code on build time.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;.:/app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;After we have set and confirmed all of the configuration above we can just simply execute:&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;source &lt;/span&gt;docker_env &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-compose up &lt;span class=&quot;nt&quot;&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;If everything went fine we should see something similar appear on stdoud.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;blog_1  | Configuration file: /app/_config.yml
blog_1  |             Source: /app
blog_1  |        Destination: /app/_site
blog_1  |  Incremental build: disabled. Enable with &lt;span class=&quot;nt&quot;&gt;--incremental&lt;/span&gt;
blog_1  |       Generating...
blog_1  |        Jekyll Feed: Generating feed &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;posts
blog_1  |                     &lt;span class=&quot;k&quot;&gt;done in &lt;/span&gt;0.562 seconds.
blog_1  |  Auto-regeneration: enabled &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'/app'&lt;/span&gt;
blog_1  |     Server address: http://0.0.0.0:5000
blog_1  |   Server running... press ctrl-c to stop.

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;
This means that the application is running locally and if we open &lt;a href=&quot;http://localhost:5000&quot;&gt;http://localhost:5000&lt;/a&gt; we will see our Jekyll blog.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
Some of you might have noticed that I have not created a dedicated user inside the container. I have just instructed the container to use my system UID and GID (which are both 1000). This is because I have found that docker has no problems running any application with a UID and GID that do not exist inside it.
We can see this by running the docker image used in this blog post. Looking at the users on the container, it clearly shows that a user with UID and GID 1000 is not present.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-ash&quot;&gt;$ docker run -it ruby:2.5.3-alpine3.8 sh

/ # cat /etc/passwd | sort -n -t: -k3
root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/bin/sh
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/spool/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
postgres:x:70:70::/var/lib/postgresql:/bin/sh
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
/ #
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;With the solution outlined above we have successfully implemented the host data persistence pattern. We have also heightened our security, solely because the user hasn’t been created in the first place and therefore it has no home directory, no shell or anything else that might get copied from /etc/skel, that is if we do not take extra precaution whilst creating it. As an added bonus we have our application running as a non root user which is one of the simplest and straightforward security practices for docker.&lt;/p&gt;

&lt;p&gt;All the configuration files that are mentioned above can be found here &lt;a href=&quot;https://github.com/parabolic/parabolic.github.io&quot;&gt;https://github.com/parabolic/parabolic.github.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
That is all for now. If you find this blog post helpful or if you think it can be improved in anyway, I would kindly ask you to provide your feedback and comments! Also please do not forget to share it if you like it!&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
  &lt;a href=&quot;https://twitter.com/share?ref_src=twsrc%5Etfw&quot; class=&quot;twitter-share-button&quot; data-size=&quot;large&quot; data-via=&quot;kikolanikola&quot; data-hashtags=&quot;cloudlad&quot; data-show-count=&quot;false&quot;&gt;Tweet&lt;/a&gt;&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;/p&gt;</content><author><name>Nikola</name></author><category term="docker" /><category term="volumes" /><category term="pattern" /><category term="data" /><category term="layers" /><category term="container" /><category term="jekyll" /><category term="docker-compose" /><category term="local-development" /><category term="development" /><summary type="html">By default all files created inside a container are stored on a writable container layer.</summary></entry></feed>