Skip to content

feat(examples/templates): add GCP VM devcontainer template #11246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions examples/templates/gcp-devcontainer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
display_name: Google Compute Engine (Devcontainer)
description: Provision a Devcontainer on Google Compute Engine instances as Coder workspaces
icon: ../../../site/static/icon/gcp.png
maintainer_github: coder
verified: true
tags: [vm, linux, gcp, devcontainer]
---

# Remote Development in a Devcontainer on Google Compute Engine

![Architecture Diagram](./architecture.svg)

## Prerequisites

### Authentication

This template assumes that coderd is run in an environment that is authenticated
with Google Cloud. For example, run `gcloud auth application-default login` to
import credentials on the system and user running coderd. For other ways to
authenticate [consult the Terraform
docs](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/getting_started#adding-credentials).

Coder requires a Google Cloud Service Account to provision workspaces. To create
a service account:

1. Navigate to the [CGP
console](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create),
and select your Cloud project (if you have more than one project associated
with your account)

1. Provide a service account name (this name is used to generate the service
account ID)

1. Click **Create and continue**, and choose the following IAM roles to grant to
the service account:

- Compute Admin
- Service Account User

Click **Continue**.

1. Click on the created key, and navigate to the **Keys** tab.

1. Click **Add key** > **Create new key**.

1. Generate a **JSON private key**, which will be what you provide to Coder
during the setup process.

## Architecture

This template provisions the following resources:

- GCP VM (persistent)
- GCP Disk (persistent, mounted to root)

Coder persists the root volume. The full filesystem is preserved when the workspace restarts.

> **Note**
> This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.

## code-server

`code-server` is installed via the [`code-server`](https://registry.coder.com/modules/code-server) registry module. Please check [Coder Registry](https://registry.coder.com) for a list of all modules and templates.
8 changes: 8 additions & 0 deletions examples/templates/gcp-devcontainer/architecture.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
206 changes: 206 additions & 0 deletions examples/templates/gcp-devcontainer/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
terraform {
required_providers {
coder = {
source = "coder/coder"
}
google = {
source = "hashicorp/google"
}
}
}

provider "coder" {
}

variable "project_id" {
description = "Which Google Compute Project should your workspace live in?"
}

data "coder_parameter" "zone" {
name = "zone"
display_name = "Zone"
description = "Which zone should your workspace live in?"
type = "string"
icon = "/emojis/1f30e.png"
default = "us-central1-a"
mutable = false
option {
name = "North America (Northeast)"
value = "northamerica-northeast1-a"
icon = "/emojis/1f1fa-1f1f8.png"
}
option {
name = "North America (Central)"
value = "us-central1-a"
icon = "/emojis/1f1fa-1f1f8.png"
}
option {
name = "North America (West)"
value = "us-west2-c"
icon = "/emojis/1f1fa-1f1f8.png"
}
option {
name = "Europe (West)"
value = "europe-west4-b"
icon = "/emojis/1f1ea-1f1fa.png"
}
option {
name = "South America (East)"
value = "southamerica-east1-a"
icon = "/emojis/1f1e7-1f1f7.png"
}
}

provider "google" {
zone = data.coder_parameter.zone.value
project = var.project_id
}

data "google_compute_default_service_account" "default" {
}

data "coder_workspace" "me" {
}

resource "google_compute_disk" "root" {
name = "coder-${data.coder_workspace.me.id}-root"
type = "pd-ssd"
image = "debian-cloud/debian-12"
lifecycle {
ignore_changes = [name, image]
}
}

data "coder_parameter" "repo_url" {
name = "repo_url"
display_name = "Repository URL"
default = "https://github.com/coder/envbuilder-starter-devcontainer"
description = "Repository URL"
mutable = true
}

resource "coder_agent" "dev" {
count = data.coder_workspace.me.start_count
arch = "amd64"
auth = "token"
os = "linux"
dir = "/worskpaces"
connection_timeout = 0

metadata {
key = "cpu"
display_name = "CPU Usage"
interval = 5
timeout = 5
script = "coder stat cpu"
}
metadata {
key = "memory"
display_name = "Memory Usage"
interval = 5
timeout = 5
script = "coder stat mem"
}
metadata {
key = "disk"
display_name = "Disk Usage"
interval = 5
timeout = 5
script = "coder stat disk"
}
}

module "code-server" {
count = data.coder_workspace.me.start_count
source = "https://registry.coder.com/modules/code-server"
agent_id = coder_agent.dev[0].id
}

resource "google_compute_instance" "vm" {
name = "coder-${lower(data.coder_workspace.me.owner)}-${lower(data.coder_workspace.me.name)}-root"
machine_type = "e2-medium"
# data.coder_workspace.me.owner == "default" is a workaround to suppress error in the terraform plan phase while creating a new workspace.
desired_status = (data.coder_workspace.me.owner == "default" || data.coder_workspace.me.start_count == 1) ? "RUNNING" : "TERMINATED"

network_interface {
network = "default"
access_config {
// Ephemeral public IP
}
}

boot_disk {
auto_delete = false
source = google_compute_disk.root.name
}

service_account {
email = data.google_compute_default_service_account.default.email
scopes = ["cloud-platform"]
}

metadata = {
# The startup script runs as root with no $HOME environment set up, so instead of directly
# running the agent init script, create a user (with a homedir, default shell and sudo
# permissions) and execute the init script as that user.
startup-script = <<-META
#!/usr/bin/env sh
set -eux

# If user does not exist, create it and set up passwordless sudo
if ! id -u "${local.linux_user}" >/dev/null 2>&1; then
useradd -m -s /bin/bash "${local.linux_user}"
echo "${local.linux_user} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/coder-user
fi

# Check for Docker, install if not present
if ! command -v docker &> /dev/null
then
echo "Docker not found, installing..."
curl -fsSL https://get.docker.com -o get-docker.sh && sudo sh get-docker.sh 2>&1 >/dev/null
sudo usermod -aG docker ${local.linux_user}
newgrp docker
else
echo "Docker is already installed."
fi
# Start envbuilder
docker run --rm \
-v /tmp/envbuilder:/workspaces \
-e CODER_AGENT_TOKEN="${try(coder_agent.dev[0].token, "")}" \
-e CODER_AGENT_URL="${data.coder_workspace.me.access_url}" \
-e GIT_URL="${data.coder_parameter.repo_url.value}" \
-e INIT_SCRIPT="echo ${base64encode(try(coder_agent.dev[0].init_script, ""))} | base64 -d | sh" \
-e FALLBACK_IMAGE="codercom/enterprise-base:ubuntu" \
ghcr.io/coder/envbuilder
META
}
}

locals {
# Ensure Coder username is a valid Linux username
linux_user = lower(substr(data.coder_workspace.me.owner, 0, 32))
}

resource "coder_metadata" "workspace_info" {
count = data.coder_workspace.me.start_count
resource_id = google_compute_instance.vm.id

item {
key = "type"
value = google_compute_instance.vm.machine_type
}

item {
key = "zone"
value = data.coder_parameter.zone.value
}
}

resource "coder_metadata" "home_info" {
resource_id = google_compute_disk.root.id

item {
key = "size"
value = "${google_compute_disk.root.size} GiB"
}
}