Development

From Terraform to GitOps to Pulumi

The Platform Engineers Journey

In a previous post, we talked about the increasing adoption of Platform Engineering teams. The post covered topics such as defining Platform Engineering and the roles and responsibilities of the team. 

When building an internal platform, a clear goal that many teams want to achieve is:

Deliver the deployment experience of Heroku while keeping the capability to customize the stack to fit the different tools we use today and will adopt tomorrow. All without changing the developer experience.

Even though this is key to a successful platform team, this responsibility increases complexity, costs, support time, and more. Not to mention that this can be a long, very long journey.

So the question is, how do you implement a flexible platform and keep developers away from the complexity?

A Cloud-Native Case

Let’s take Terraform, for example. For years now, Terraform has been the tool of choice for provisioning infrastructure. Although it may not be the best solution for deploying apps, many teams have built application-level automation using it.

With cloud-native, teams deploy applications more often, distributed across many clusters, and come up with more advanced requirements and this is driving many DevOps teams to explore GitOps based solutions, such as ArgoCD or FluxCD. 

What’s the complexity? Deploying applications using GitOps is quite different from deploying them using Terraform. 

By introducing GitOps, the DevOps team changes the developer experience, decreasing adoption speed, forcing the developer to deal with Kubernetes-related complexities, creating pipeline complexity, and introducing different application and policies definitions.

Using a standard application definition layer

Using an application layer enables you to implement a declarative and standard definition that your teams can leverage to deploy, manage, and secure applications. This approach detaches the application from the underlying infrastructure, pipeline, or IaC provider. 

Just like Infrastructure as Code (IaC) did for infrastructure management, using an Application as Code (AaC) approach will help you deliver a consistent application deployment experience to your developers. At the same time, you are free to try and adopt new technologies.

From Terraform to GitOps to Pulumi

If you are part of a medium to large organization, teams will inevitably use or request different technologies. Let’s see how an Application as Code (AaC) layer could help with that:

Let’s say you have this structure:

It’s quite clear the complexity that you have to deal with as a Platform Engineer. We are not even talking about all the other additional components you have to manage, such as incident management integration, monitoring, and more.

Having a mixed stack is not the problem. You should enable your teams to use what’s best to have their applications delivered. The problem is not having a layer to standardize application deployment and policies across different environments.

By implementing an Application as Code (AaC) layer would allow you to do this:

Development team 1 wants to use Terraform to deploy their applications on Kubernetes and VMs. (Yes, yes, VMs are still around and will stay forever, my friend!).

Here is the template you would give them to have their applications deployed:

terraform {
  required_providers {
    shipa = {
      version = "0.0.5"
      source = "shipa-corp/shipa"
    }
  }
}
provider "shipa" {
  host = "https://target.shipa.cloud:8080"
  token = "<your-shipa-token>"
}
resource "shipa_app" "app1" {
  app {
    name = "terraform-app"
    teamowner = "dev"
    framework = "dev-framework"
  }
}
resource "shipa_app_deploy" "deploy1" {
  app = "terraform-app"
  deploy {
    image = "docker.io/shipasoftware/bulletinboard:1.0"
  }
}

That’s it! Notice that in the example above, the developer is not involved in the underlying infrastructure complexity, uses an application-level definition, and gets the chance to use the tool they wanted, Terraform.

In addition to their application image definition, they also define which Policy Framework the application should use when deploying. More on this later!

Now, Development Team 2 says they want to use Pulumi and Kubernetes for their project.

Here is what you would give them as an application template:

import * as pulumi from "@pulumi/pulumi";
import * as shipa from "@shipa-corp/pulumi";


const app = new shipa.App("app", {
    app: {
        name: "pulumi-app-1",
        framework: "policy-fw-name",
        teamowner: "shipa-team"
    }
});

export const appName = app.app.name;

const appDeploy = new shipa.AppDeploy("app-deploy", {
    app: "pulumi-app-1",
    deploy: {
        image: "docker.io/shipasoftware/bulletinboard:1.0"
    }
});

export const appName = appDeploy.app;

Like the previous example, this definition is very application-focused. Aside from the IaC tool boilerplate, the application definition used is the same as that used with Terraform to deploy across Kubernetes and VMs.

Development Team 3 is cutting edge and wants to work with GitOps and deploy to EKS.

Here is the template you would give them:

apiVersion: shipa.crossplane.io/v1alpha1
kind: App
metadata:
  name: app-create
spec:
  forProvider:
    name: booking-service
    teamowner: shipa-admin-team
    framework: cp-prod

---

apiVersion: shipa.crossplane.io/v1alpha1
kind: AppDeploy
metadata:
  name: app-deploy
spec:
  forProvider:
    app: booking-service
    image: "docker.io/shipasoftware/bulletinboard:1.0"

I guess at this point, I don’t need to say the definition is similar to the others 🙂 

Additionally, when you deploy these using Shipa as your Application as Code (AaC) layer, Shipa will automatically provision an endpoint, hook up monitoring, run policies, and more. All without involving your developers in any complexity.

Policy Management

Just like the development team, the platform team also wants to keep trying and implementing new tools. Here is how an Application as Code (AaC) layer like Shipa can help you address policy management in an evolving environment:

Terraform example:

  required_providers {
    shipa = {
      version = "0.0.5"
      source = "shipa-corp/shipa"
    }
  }
}

provider "shipa" {
  host = "https://target.shipa.cloud:8080"
  token = "<your-shipa-token>"
}

resource "shipa_framework" "framework1" {
  framework {
    name = "cinema-services"
    resources {
      general {
        setup {
          public = false
          default = false
        }
        security {
          disable_scan = true
          scan_platform_layers = false
          ignore_components = ["apt", "bash", "..."]
          ignore_cves = ["CVE-2020-27350", "CVE-2011-3374", "..."]
        }
        app_quota {
          limit = "5"
        }
        access {
          append = ["dev-team1"]
        }
        plan {
          name = "shipa-plan"
        }
        network_policy {
          ingress {
             policy_mode = "deny-all"
          }
          egress {
             policy_mode = "allow-all"
          }
        }
        container_policy {
          allowed_hosts = ["docker.io/shipasoftware", "docker.io/shiparepo"]
        }
      }
    }
  }
}

GitOps example:

apiVersion: shipa.crossplane.io/v1alpha1
kind: Framework
metadata:
  name: cp-dev
spec:
  forProvider:
    shipaFramework: cp-dev
    resources:
      general:
        setup:
          provisioner: kubernetes
        appQuota:
          limit: "4"
        plan:
          name: "plan name"
        security:
          disableScan: true
          scanPlatformLayers: false
          ignoreComponents: ["apt", "bash", "..."]
          ignoreCves: ["CVE-2020-27350", "CVE-2011-3374", "..."]
        networkPolicy:
          ingress:
            policy_mode: deny-all
          egress:
            policy_mode: allow-all
          disableAppPolicies: false
        containerPolicy:
          allowedHosts: ["docker.io/shipasoftware", "docker.io/shiparepo"]

Both tools use precisely the same definitions and are again very application-oriented. Without dealing with the underlying cluster complexity, you define policies around network traffic, registry control, RBAC, and more.

Application as Code (AaC) allows you to evolve and use different IaC tools, different cluster providers, and more while keeping policies and control organized and scalable.

Conclusion

As people say, “the only constant is change.” To succeed in implementing a Platform Engineering team and an internal platform, you will need to enable developers to move fast. You need to detach applications from the infrastructure with the proper controls, so your team can keep moving forward.