Skip to content

Commit 3fd7b47

Browse files
authored
feat: dynamic username template (#261)
## Description this PR adds a new template that creates & runs Coder workspaces on K8s with the user's Coder `username` as the Linux UID. a commonly requested use-case by customers. ## Type of Change - [x] New module - [ ] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information <!-- Delete this section if not applicable --> **Path:** `registry/ericpaulsen/templates/k8s-pod-username.tf` ## Testing & Validation - [x] Changes tested locally
1 parent e1f077d commit 3fd7b47

File tree

4 files changed

+394
-0
lines changed

4 files changed

+394
-0
lines changed
23.3 KB
Loading

registry/ericpaulsen/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
display_name: "Eric Paulsen"
3+
bio: "Field CTO, EMEA @ Coder"
4+
avatar_url: "./.images/avatar.png"
5+
github: "ericpaulsen"
6+
linkedin: "https://www.linkedin.com/in/ericpaulsen17" # Optional
7+
website: "https://ericpaulsen.io" # Optional
8+
support_email: "[email protected]" # Optional
9+
status: "community"
10+
---
11+
12+
# Eric Paulsen
13+
14+
I'm Eric Paulsen, Coder's EMEA Field CTO based in London, originating from Miami.
15+
Outside of working with our customers, I enjoy teaching myself things,
16+
playing volleyball, and dabbling in a bit of DJing & photography.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
display_name: Kubernetes (Deployment) with Dynamic Username
3+
description: Provision Kubernetes Deployments as Coder workspaces with your Username
4+
icon: ../../../site/static/icon/k8s.png
5+
verified: true
6+
tags: [kubernetes, container, username]
7+
---
8+
9+
# Remote development on Kubernetes with dynamic usernames
10+
11+
Provision Kubernetes Pods as [Coder workspaces](https://coder.com/docs/workspaces) with this example template. This template
12+
will run the workspace container as a non-root UID using your Coder username.
13+
14+
Here is the entrypoint logic in the template that enables Coder to source your username and write it to the Ubuntu operating system at start-up.
15+
16+
> These commands may differ if you run your workspace image with a distro other than Ubuntu.
17+
18+
```terraform
19+
command = ["sh", "-c", <<EOF
20+
# Create user and setup home directory
21+
sudo useradd ${data.coder_workspace_owner.me.name} --home=/home/${data.coder_workspace_owner.me.name} --shell=/bin/bash --uid=1001 --user-group
22+
sudo chown -R ${data.coder_workspace_owner.me.name}:${data.coder_workspace_owner.me.name} /home/${data.coder_workspace_owner.me.name}
23+
24+
# Switch to user and run agent
25+
exec sudo --preserve-env=CODER_AGENT_TOKEN -u ${data.coder_workspace_owner.me.name} sh -c '${coder_agent.main.init_script}'
26+
EOF
27+
]
28+
```
29+
30+
<!-- TODO: Add screenshot -->
31+
32+
## Prerequisites
33+
34+
### Infrastructure
35+
36+
**Cluster**: This template requires an existing Kubernetes cluster
37+
38+
**Container Image**: This template uses the [codercom/enterprise-base:ubuntu image](https://github.com/coder/enterprise-images/tree/main/images/base) with some dev tools preinstalled. To add additional tools, extend this image or build it yourself.
39+
40+
### Authentication
41+
42+
This template authenticates using a `~/.kube/config`, if present on the server, or via built-in authentication if the Coder provisioner is running on Kubernetes with an authorized ServiceAccount. To use another [authentication method](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs#authentication), edit the template.
43+
44+
## Architecture
45+
46+
This template provisions the following resources:
47+
48+
- Kubernetes Deployment (ephemeral)
49+
- Kubernetes persistent volume claim (persistent on `/home/${username}`, where `${username}` is your Coder username)
50+
51+
This means, when the workspace restarts, any tools or files outside of the home directory are not persisted. To pre-bake tools into the workspace (e.g. `python3`), modify the container image. Alternatively, individual developers can [personalize](https://coder.com/docs/dotfiles) their workspaces with dotfiles.
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
terraform {
2+
required_providers {
3+
coder = {
4+
source = "coder/coder"
5+
}
6+
kubernetes = {
7+
source = "hashicorp/kubernetes"
8+
}
9+
}
10+
}
11+
12+
provider "coder" {
13+
}
14+
15+
variable "use_kubeconfig" {
16+
type = bool
17+
description = <<-EOF
18+
Use host kubeconfig? (true/false)
19+
20+
Set this to false if the Coder host is itself running as a Pod on the same
21+
Kubernetes cluster as you are deploying workspaces to.
22+
23+
Set this to true if the Coder host is running outside the Kubernetes cluster
24+
for workspaces. A valid "~/.kube/config" must be present on the Coder host.
25+
EOF
26+
default = false
27+
}
28+
29+
variable "namespace" {
30+
type = string
31+
description = "The Kubernetes namespace to create workspaces in (must exist prior to creating workspaces). If the Coder host is itself running as a Pod on the same Kubernetes cluster as you are deploying workspaces to, set this to the same namespace."
32+
}
33+
34+
data "coder_parameter" "cpu" {
35+
name = "cpu"
36+
display_name = "CPU"
37+
description = "The number of CPU cores"
38+
default = "2"
39+
icon = "/icon/memory.svg"
40+
mutable = true
41+
option {
42+
name = "2 Cores"
43+
value = "2"
44+
}
45+
option {
46+
name = "4 Cores"
47+
value = "4"
48+
}
49+
option {
50+
name = "6 Cores"
51+
value = "6"
52+
}
53+
option {
54+
name = "8 Cores"
55+
value = "8"
56+
}
57+
}
58+
59+
data "coder_parameter" "memory" {
60+
name = "memory"
61+
display_name = "Memory"
62+
description = "The amount of memory in GB"
63+
default = "2"
64+
icon = "/icon/memory.svg"
65+
mutable = true
66+
option {
67+
name = "2 GB"
68+
value = "2"
69+
}
70+
option {
71+
name = "4 GB"
72+
value = "4"
73+
}
74+
option {
75+
name = "6 GB"
76+
value = "6"
77+
}
78+
option {
79+
name = "8 GB"
80+
value = "8"
81+
}
82+
}
83+
84+
data "coder_parameter" "home_disk_size" {
85+
name = "home_disk_size"
86+
display_name = "Home disk size"
87+
description = "The size of the home disk in GB"
88+
default = "10"
89+
type = "number"
90+
icon = "/emojis/1f4be.png"
91+
mutable = false
92+
validation {
93+
min = 1
94+
max = 99999
95+
}
96+
}
97+
98+
provider "kubernetes" {
99+
# Authenticate via ~/.kube/config or a Coder-specific ServiceAccount, depending on admin preferences
100+
config_path = var.use_kubeconfig == true ? "~/.kube/config" : null
101+
}
102+
103+
data "coder_workspace" "me" {}
104+
data "coder_workspace_owner" "me" {}
105+
106+
module "vscode-web" {
107+
count = data.coder_workspace.me.start_count
108+
source = "registry.coder.com/coder/vscode-web/coder"
109+
version = "1.3.1"
110+
agent_id = coder_agent.main.id
111+
accept_license = true
112+
}
113+
114+
resource "coder_agent" "main" {
115+
os = "linux"
116+
arch = "amd64"
117+
118+
# The following metadata blocks are optional. They are used to display
119+
# information about your workspace in the dashboard. You can remove them
120+
# if you don't want to display any information.
121+
# For basic resources, you can use the `coder stat` command.
122+
# If you need more control, you can write your own script.
123+
metadata {
124+
display_name = "CPU Usage"
125+
key = "0_cpu_usage"
126+
script = "coder stat cpu"
127+
interval = 10
128+
timeout = 1
129+
}
130+
131+
metadata {
132+
display_name = "RAM Usage"
133+
key = "1_ram_usage"
134+
script = "coder stat mem"
135+
interval = 10
136+
timeout = 1
137+
}
138+
139+
metadata {
140+
display_name = "Home Disk"
141+
key = "3_home_disk"
142+
script = "coder stat disk --path $${HOME}"
143+
interval = 60
144+
timeout = 1
145+
}
146+
147+
metadata {
148+
display_name = "CPU Usage (Host)"
149+
key = "4_cpu_usage_host"
150+
script = "coder stat cpu --host"
151+
interval = 10
152+
timeout = 1
153+
}
154+
155+
metadata {
156+
display_name = "Memory Usage (Host)"
157+
key = "5_mem_usage_host"
158+
script = "coder stat mem --host"
159+
interval = 10
160+
timeout = 1
161+
}
162+
163+
metadata {
164+
display_name = "Load Average (Host)"
165+
key = "6_load_host"
166+
# get load avg scaled by number of cores
167+
script = <<EOT
168+
echo "`cat /proc/loadavg | awk '{ print $1 }'` `nproc`" | awk '{ printf "%0.2f", $1/$2 }'
169+
EOT
170+
interval = 60
171+
timeout = 1
172+
}
173+
}
174+
175+
resource "kubernetes_persistent_volume_claim" "home" {
176+
metadata {
177+
name = "coder-${data.coder_workspace.me.id}-home"
178+
namespace = var.namespace
179+
labels = {
180+
"app.kubernetes.io/name" = "coder-pvc"
181+
"app.kubernetes.io/instance" = "coder-pvc-${data.coder_workspace.me.id}"
182+
"app.kubernetes.io/part-of" = "coder"
183+
//Coder-specific labels.
184+
"com.coder.resource" = "true"
185+
"com.coder.workspace.id" = data.coder_workspace.me.id
186+
"com.coder.workspace.name" = data.coder_workspace.me.name
187+
"com.coder.user.id" = data.coder_workspace_owner.me.id
188+
"com.coder.user.username" = data.coder_workspace_owner.me.name
189+
}
190+
annotations = {
191+
"com.coder.user.email" = data.coder_workspace_owner.me.email
192+
}
193+
}
194+
wait_until_bound = false
195+
spec {
196+
access_modes = ["ReadWriteOnce"]
197+
resources {
198+
requests = {
199+
storage = "${data.coder_parameter.home_disk_size.value}Gi"
200+
}
201+
}
202+
}
203+
}
204+
205+
resource "kubernetes_deployment" "main" {
206+
count = data.coder_workspace.me.start_count
207+
depends_on = [
208+
kubernetes_persistent_volume_claim.home
209+
]
210+
wait_for_rollout = false
211+
metadata {
212+
name = "coder-${data.coder_workspace.me.id}"
213+
namespace = var.namespace
214+
labels = {
215+
"app.kubernetes.io/name" = "coder-workspace"
216+
"app.kubernetes.io/instance" = "coder-workspace-${data.coder_workspace.me.id}"
217+
"app.kubernetes.io/part-of" = "coder"
218+
"com.coder.resource" = "true"
219+
"com.coder.workspace.id" = data.coder_workspace.me.id
220+
"com.coder.workspace.name" = data.coder_workspace.me.name
221+
"com.coder.user.id" = data.coder_workspace_owner.me.id
222+
"com.coder.user.username" = data.coder_workspace_owner.me.name
223+
}
224+
annotations = {
225+
"com.coder.user.email" = data.coder_workspace_owner.me.email
226+
}
227+
}
228+
229+
spec {
230+
replicas = 1
231+
selector {
232+
match_labels = {
233+
"app.kubernetes.io/name" = "coder-workspace"
234+
"app.kubernetes.io/instance" = "coder-workspace-${data.coder_workspace.me.id}"
235+
"app.kubernetes.io/part-of" = "coder"
236+
"com.coder.resource" = "true"
237+
"com.coder.workspace.id" = data.coder_workspace.me.id
238+
"com.coder.workspace.name" = data.coder_workspace.me.name
239+
"com.coder.user.id" = data.coder_workspace_owner.me.id
240+
"com.coder.user.username" = data.coder_workspace_owner.me.name
241+
}
242+
}
243+
strategy {
244+
type = "Recreate"
245+
}
246+
247+
template {
248+
metadata {
249+
labels = {
250+
"app.kubernetes.io/name" = "coder-workspace"
251+
"app.kubernetes.io/instance" = "coder-workspace-${data.coder_workspace.me.id}"
252+
"app.kubernetes.io/part-of" = "coder"
253+
"com.coder.resource" = "true"
254+
"com.coder.workspace.id" = data.coder_workspace.me.id
255+
"com.coder.workspace.name" = data.coder_workspace.me.name
256+
"com.coder.user.id" = data.coder_workspace_owner.me.id
257+
"com.coder.user.username" = data.coder_workspace_owner.me.name
258+
}
259+
}
260+
spec {
261+
262+
263+
container {
264+
name = "dev"
265+
image = "codercom/enterprise-base:ubuntu"
266+
image_pull_policy = "Always"
267+
command = ["sh", "-c", <<EOF
268+
# Create user and setup home directory
269+
sudo useradd ${lower(data.coder_workspace_owner.me.name)} --home=/home/${lower(data.coder_workspace_owner.me.name)} --shell=/bin/bash --uid=1001 --user-group
270+
sudo chown -R ${lower(data.coder_workspace_owner.me.name)}:${lower(data.coder_workspace_owner.me.name)} /home/${lower(data.coder_workspace_owner.me.name)}
271+
272+
# Switch to user and run agent
273+
exec sudo --preserve-env=CODER_AGENT_TOKEN -u ${lower(data.coder_workspace_owner.me.name)} sh -c '${coder_agent.main.init_script}'
274+
EOF
275+
]
276+
env {
277+
name = "CODER_AGENT_TOKEN"
278+
value = coder_agent.main.token
279+
}
280+
resources {
281+
requests = {
282+
"cpu" = "250m"
283+
"memory" = "512Mi"
284+
}
285+
limits = {
286+
"cpu" = "${data.coder_parameter.cpu.value}"
287+
"memory" = "${data.coder_parameter.memory.value}Gi"
288+
}
289+
}
290+
volume_mount {
291+
mount_path = "/home/${lower(data.coder_workspace_owner.me.name)}"
292+
name = "home"
293+
read_only = false
294+
}
295+
}
296+
297+
volume {
298+
name = "home"
299+
persistent_volume_claim {
300+
claim_name = kubernetes_persistent_volume_claim.home.metadata.0.name
301+
read_only = false
302+
}
303+
}
304+
305+
affinity {
306+
// This affinity attempts to spread out all workspace pods evenly across
307+
// nodes.
308+
pod_anti_affinity {
309+
preferred_during_scheduling_ignored_during_execution {
310+
weight = 1
311+
pod_affinity_term {
312+
topology_key = "kubernetes.io/hostname"
313+
label_selector {
314+
match_expressions {
315+
key = "app.kubernetes.io/name"
316+
operator = "In"
317+
values = ["coder-workspace"]
318+
}
319+
}
320+
}
321+
}
322+
}
323+
}
324+
}
325+
}
326+
}
327+
}

0 commit comments

Comments
 (0)