Is Ephemeral Environments Right for Your Team?
Consider these key questions to determine if ephemeral environments would benefit your workflow:
If you answered yes to any of these questions, implementing ephemeral environments with Ctrlplane could significantly improve your development workflow and reduce operational overhead.
Scenario
A development team needs to test feature branches in isolated environments
before merging to main. Each developer needs:
- A complete replica of the production environment
- Automatic cleanup when pull requests are merged
- Cost optimization for cloud resources
- Integration with their CI/CD pipeline
In this example we will be using the following technologies
- Github Actions for CI/CD
- Kubernetes (GKE) for containerization
We are going to deploy a simple nginx application to the cluster and then test
it.
Here is a high level overview of the workflow:
1. Pull Request Resource Creation
When using Ctrlplane for any system, you must first determine what resources represent the existence of your deployment.
In this case, we’ll use GitHub pull requests to determine when ephemeral environments should be created and destroyed.
This approach means Ctrlplane will follow the PR lifecycle:
- Creating the environment when a PR is opened
- Destroying it when the PR is merged or closed
To implement this workflow, we’ll create a GitHub workflow that tracks PR state changes and updates resources in Ctrlplane accordingly.
Here is what it does:
-
When a pull request is opened or reopened:
- Creates/updates a resource in Ctrlplane with metadata about the PR
- Includes details like repository, branch, PR number, and author
- Links back to the GitHub PR for reference
-
When a pull request is closed:
- Automatically deletes the corresponding resource from Ctrlplane
- Helps clean up any associated ephemeral environments
.github/workflows/create-git-resource.yml
name: Git Ref Resource Action
concurrency:
group: ${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: false
on:
pull_request:
types: [opened, closed, reopened]
jobs:
git-ref-target:
runs-on: ubuntu-latest
steps:
- name: Install Ctrlplane CLI
uses: ctrlplanedev/cli@main
with:
workspace: ${{ secrets.CTRLPLANE_WORKSPACE_ID }}
api_key: ${{ secrets.CTRLPLANE_API_KEY }}
- name: Create Resource for Pull Request
if: github.event_name == 'pull_request' && (github.event.pull_request.state == 'open' || github.event.pull_request.state == 'reopened')
run: |
ctrlc api upsert resource \
--version "github/v1" \
--kind "PullRequest" \
--identifier "github/${{ github.event.repository.owner.login }}/${{ github.event.repository.name }}/${{ github.event.pull_request.number }}" \
--name "${{ github.event.repository.name }}/${{ github.event.pull_request.head.ref }}" \
--config "type=github" \
--config "branch=${{ github.event.pull_request.head.ref }}" \
--config "repo=${{ github.event.repository.owner.login }}/${{ github.event.repository.name }}" \
--config "pullRequest=${{ github.event.pull_request.number }}" \
--metadata "github/repo=${{ github.event.repository.owner.login }}/${{ github.event.repository.name }}" \
--metadata "github/pull-request=${{ github.event.pull_request.number }}" \
--metadata "git/branch=${{ github.event.pull_request.head.ref }}" \
--metadata "git/repo=${{ github.event.repository.owner.login }}/${{ github.event.repository.name }}" \
--metadata "git/author=$(git log -1 --pretty=format:%an)" \
--metadata "git/author-email=${{ github.event.pull_request.head.user.email }}" \
--link "GitHub Pull Request=https://github.com/${{ github.event.repository.owner.login }}/${{ github.event.repository.name }}/pull/${{ github.event.pull_request.number }}"
- name: Delete Resource
if: github.event_name == 'pull_request' && github.event.pull_request.state == 'closed'
run: |
ctrlc api delete resource \
--identifier "github/${{ github.event.repository.owner.login }}/${{ github.event.repository.name }}/${{ github.event.pull_request.number }}"
2. Infrastructure Creation
Once the pull request resource is created, we now determine what pipelines need
to be take give the state of the Pull Request. In our scenario we will create a
GKE cluster for each PR.
.github/workflows/deploy-gke-cluster.yml
name: Deploy GKE Cluster
run-name: Deploy GKE Cluster [${{ inputs.job_id }}]
on:
workflow_dispatch:
inputs:
job_id:
description: "Job ID"
required: true
jobs:
create-gke:
runs-on: ubuntu-latest
steps:
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v1
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v1
- id: ctrlplane
uses: ctrlplanedev/ctrlplane/github/get-job-inputs@main
with:
job_id: ${{ inputs.job_id }}
api_key:
${{ secrets.CTRLPLANE_API_KEY }}
# the pull request number, this was set in the config body
# when creating the pull request resource:
# `--config "pullRequest=${{ github.event.pull_request.number }}"`
required_outputs: |
resource_config_pullRequest
- name: Install Ctrlplane CLI
uses: ctrlplanedev/cli@main
with:
workspace: ${{ secrets.CTRLPLANE_WORKSPACE_ID }}
api_key: ${{ secrets.CTRLPLANE_API_KEY }}
- name: Create GKE Cluster
run: |
# Create a unique cluster name based on PR
CLUSTER_NAME="pr-${{ outputs.ctrlplane.resource_config_pullRequest }}"
REGION="us-central1"
# Create GKE cluster
gcloud container clusters create $CLUSTER_NAME \
--region $REGION \
--num-nodes 1 \
--machine-type e2-standard-2
# Get cluster credentials
gcloud container clusters get-credentials $CLUSTER_NAME --region $REGION
# Create Ctrlplane resource for the cluster
ctrlc api upsert resource \
--version "gke/v1" \
--kind "Cluster" \
--identifier "gke/$CLUSTER_NAME" \
--name "$CLUSTER_NAME" \
--config "project=${{ secrets.GCP_PROJECT_ID }}" \
--config "region=$REGION" \
--config "cluster=$CLUSTER_NAME" \
--metadata "pr=${{ github.event.pull_request.number }}" \
--metadata "branch=${{ github.event.pull_request.head.ref }}" \
--link "GCP Console=https://console.cloud.google.com/kubernetes/clusters/details/$REGION/$CLUSTER_NAME"
If you wanted to speed up the creation of the cluster, you could instead
deploy to a namespace in an existing cluster.
3. Application Deployment
Lets create a new github action that will deploy our Nginx application to the cluster.
.github/workflows/deploy-nginx.yml
name: Deploy Nginx
run-name: Deploy Nginx [${{ inputs.job_id }}]
on:
workflow_dispatch:
inputs:
job_id:
description: "Job ID"
required: true
jobs:
deploy-nginx:
runs-on: ubuntu-latest
steps:
- name: Deploy Nginx using Helm
run: |
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm upgrade --install nginx bitnami/nginx \
--namespace ${{ secrets.NAMESPACE }} \
--set service.type=LoadBalancer
4. Infrastructure Teardown