Repository navigation
Expand file tree
/
Copy pathkubernetes.yaml
More file actions
553 lines (550 loc) · 17.3 KB
/
Copy pathkubernetes.yaml
File metadata and controls
553 lines (550 loc) · 17.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
# Example manifest for a single-node MicroK8s cluster. Placeholders to replace
# before applying:
#
# USER the node's local username, for the hostPath mounts below
# example.lan your own DNS suffix for the two Ingress hosts
# CHANGEME the Secret and Git identity values
#
# The hostPath volumes assume a single node and are convenient for a local
# cluster; use PersistentVolumeClaims for anything scheduled across nodes.
---
apiVersion: v1
kind: Namespace
metadata:
name: openchamber
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: openchamber-storage
provisioner: microk8s.io/hostpath
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
parameters:
pvDir: /var/lib/microk8s/storage
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: openchamber-pvc
namespace: openchamber
spec:
storageClassName: openchamber-storage
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 4Gi
---
apiVersion: v1
kind: Secret
metadata:
name: openchamber-secrets
namespace: openchamber
type: Opaque
stringData:
OPENCHAMBER_PASSWORD: CHANGEME
# Login password for the code-server container. Set it to the same value as
# OPENCHAMBER_PASSWORD for a single-secret deployment.
CODE_SERVER_PASSWORD: CHANGEME
GITHUB_TOKEN: CHANGEME
# Optional: add CONTEXT7_API_KEY for higher Context7 rate limits.
---
apiVersion: v1
kind: ConfigMap
metadata:
name: git-identity
namespace: openchamber
data:
GIT_USER_NAME: CHANGEME
GIT_USER_EMAIL: CHANGEME
---
apiVersion: v1
kind: ConfigMap
metadata:
name: chrome-devtools-mcp
namespace: openchamber
data:
opencode.json: |
{
"$schema": "https://opencode.ai/config.json",
"instructions": [
"/etc/opencode/environment.md",
"/etc/opencode/chrome-devtools/kubernetes.md"
],
"mcp": {
"chrome-devtools": {
"type": "local",
"command": [
"chrome-devtools-mcp",
"--browser-url=http://127.0.0.1:9222",
"--no-usage-statistics",
"--no-performance-crux",
"--redact-network-headers"
],
"enabled": true,
"timeout": 15000
},
"context7": {
"type": "remote",
"url": "https://mcp.context7.com/mcp",
"headers": {
"Authorization": "Bearer {env:CONTEXT7_API_KEY}"
},
"enabled": true
}
}
}
kubernetes.md: |
# Kubernetes deployment context
You run in the `openchamber` container of the OpenChamber Kubernetes
Deployment. This is not a shell directly on the Kubernetes node.
- `/workspace` is a persistent hostPath mount shared with the separate
code-server Deployment. Edits affect real files on the host and are visible
to the editor. Do not treat the workspace as disposable container storage.
- OpenCode config, session data and runtime state, OpenChamber config, SSH
host keys and known-hosts state are persisted by the example's mounts.
Other container filesystem changes are not guaranteed to survive replacement.
- `/home/dev/.ssh` is mounted read-only. Do not modify or disclose its keys.
- The pod uses `hostNetwork: true`: listening services bind in the node's
network namespace. Check for port conflicts and ask before exposing a
development server. A localhost port here is not isolated from the node.
- Chrome runs in a sidecar, reached by the configured Chrome DevTools MCP
through `http://127.0.0.1:9222`. It shares networking, not the workspace or
filesystem. Its profile is ephemeral. Do not expose the DevTools endpoint.
- code-server runs in its own pod and network namespace. Its terminal has
different tools; its localhost is not this container's localhost.
- Running in Kubernetes does not imply `kubectl`, cluster credentials, or
permission to administer the cluster. Use an operator's external shell for
deployment commands unless access is explicitly provided and approved.
These facts describe the supplied Kubernetes example. Keep this document in
sync when changing its mounts, networking, or companion containers.
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: openchamber
namespace: openchamber
spec:
replicas: 1
selector:
matchLabels:
app: openchamber
template:
metadata:
labels:
app: openchamber
spec:
# hostNetwork lets sshd bind the node's :2222 directly. The SSH port is
# plain TCP, so routing it through the HTTP Ingress is not an option;
# reach it as `ssh -p 2222 dev@<node>` (or pin a NodePort instead).
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
containers:
- name: openchamber
# Published weekly by .github/workflows/publish.yml. With the
# `latest` tag, imagePullPolicy: Always is what picks up each rebuild.
# If the GHCR package is private, add an imagePullSecret with a
# read:packages token instead of relying on anonymous pulls.
image: ghcr.io/stephen-cox/opencode-container:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 3000
protocol: TCP
- name: opencode
containerPort: 4096
protocol: TCP
- name: ssh
containerPort: 2222
protocol: TCP
# Informational with hostNetwork: mosh-server binds this UDP port
# on the node when a client connects with `mosh -p 60000`.
- name: mosh
containerPort: 60000
protocol: UDP
env:
- name: OPENCHAMBER_PASSWORD
valueFrom:
secretKeyRef:
name: openchamber-secrets
key: OPENCHAMBER_PASSWORD
- name: GIT_USER_NAME
valueFrom:
configMapKeyRef:
name: git-identity
key: GIT_USER_NAME
- name: GIT_USER_EMAIL
valueFrom:
configMapKeyRef:
name: git-identity
key: GIT_USER_EMAIL
- name: GITHUB_TOKEN
valueFrom:
secretKeyRef:
name: openchamber-secrets
key: GITHUB_TOKEN
- name: CONTEXT7_API_KEY
valueFrom:
secretKeyRef:
name: openchamber-secrets
key: CONTEXT7_API_KEY
optional: true
- name: OPENCODE_CONFIG
value: /etc/opencode/chrome-devtools/opencode.json
readinessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 30
periodSeconds: 30
volumeMounts:
- name: openchamber-data
mountPath: /home/dev/.config/openchamber
subPath: config-openchamber
# Persisted sshd host key: client fingerprints stay stable across
# pod rescheduling.
- name: openchamber-data
mountPath: /etc/ssh/host-keys
subPath: ssh-host-keys
- name: opencode-share
mountPath: /home/dev/.local/share/opencode
- name: opencode-state
mountPath: /home/dev/.local/state/opencode
- name: opencode-config
mountPath: /home/dev/.config/opencode
- name: ssh
mountPath: /home/dev/.ssh
readOnly: true
- name: openchamber-data
mountPath: /home/dev/.ssh-state
subPath: ssh-state
- name: workspace
mountPath: /workspace
- name: chrome-devtools-mcp
mountPath: /etc/opencode/chrome-devtools
readOnly: true
- name: chrome
image: ghcr.io/puppeteer/puppeteer:25.9.0
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- -c
args:
- |
mkdir -p "${HOME}" /tmp/chrome-profile
CHROME="$(node --input-type=module -e 'import puppeteer from "puppeteer"; process.stdout.write(await puppeteer.executablePath())')"
exec "${CHROME}" \
--headless \
--no-sandbox \
--remote-debugging-address=127.0.0.1 \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/chrome-profile \
--no-first-run \
--no-default-browser-check \
--disable-background-networking
env:
- name: HOME
value: /tmp/chrome-home
- name: PUPPETEER_CACHE_DIR
value: /home/pptruser/.cache/puppeteer
readinessProbe:
exec:
command:
- node
- -e
- fetch('http://127.0.0.1:9222/json/version').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
livenessProbe:
exec:
command:
- node
- -e
- fetch('http://127.0.0.1:9222/json/version').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: "2"
memory: 2Gi
securityContext:
runAsNonRoot: true
runAsUser: 10042
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: chrome-tmp
mountPath: /tmp
- name: chrome-shm
mountPath: /dev/shm
volumes:
- name: openchamber-data
persistentVolumeClaim:
claimName: openchamber-pvc
- name: opencode-share
hostPath:
path: /home/USER/.local/share/opencode
type: DirectoryOrCreate
- name: opencode-state
hostPath:
path: /home/USER/.local/state/opencode
type: DirectoryOrCreate
- name: opencode-config
hostPath:
path: /home/USER/.config/opencode
type: DirectoryOrCreate
# Mounted readOnly above: the dev user is UID 1000 == the host user, so a
# writable mount would let anything in the container append to the host
# account's authorized_keys.
- name: ssh
hostPath:
path: /home/USER/.ssh
type: Directory
- name: workspace
hostPath:
path: /home/USER/workspace
type: Directory
- name: chrome-devtools-mcp
configMap:
name: chrome-devtools-mcp
- name: chrome-tmp
emptyDir:
sizeLimit: 1Gi
- name: chrome-shm
emptyDir:
medium: Memory
sizeLimit: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: openchamber-service
namespace: openchamber
spec:
type: NodePort
selector:
app: openchamber
ports:
- protocol: TCP
name: http
port: 3000
targetPort: 3000
- protocol: TCP
name: opencode
port: 4096
targetPort: 4096
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: openchamber-ingress
namespace: openchamber
annotations:
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-buffering: "off"
spec:
ingressClassName: nginx
rules:
- host: openchamber.example.lan
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: openchamber-service
port:
number: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: opencode-ingress
namespace: openchamber
annotations:
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-buffering: "off"
spec:
ingressClassName: nginx
rules:
- host: opencode.example.lan
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: openchamber-service
port:
number: 4096
# Shell access is sshd above, bound directly on the node via hostNetwork; no
# Service or Ingress entry is needed for port 2222.
---
# Browser VS Code in its own pod on purpose: VS Code's Ports panel can only
# forward ports bound inside the code-server container, so the openchamber
# workload's OpenCode API (:4096) and Chrome DevTools (:9222) stay outside its
# automatic reach. The openchamber pod runs hostNetwork for sshd — co-locating
# code-server in that pod (sidecar) would put node-bound ports directly on
# VS Code's radar. Keep it a separate Deployment.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: code-server-pvc
namespace: openchamber
spec:
storageClassName: openchamber-storage
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: code-server
namespace: openchamber
spec:
replicas: 1
selector:
matchLabels:
app: code-server
template:
metadata:
labels:
app: code-server
spec:
containers:
- name: code-server
image: codercom/code-server:latest
# `latest` floats — Always is what picks up each new release, the
# same reasoning as the openchamber image above.
imagePullPolicy: Always
ports:
- name: http
containerPort: 8080
protocol: TCP
env:
- name: PASSWORD
valueFrom:
secretKeyRef:
name: openchamber-secrets
key: CODE_SERVER_PASSWORD
- name: VSCODE_OPTIONS
value: --disable-telemetry
workingDir: /workspace
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30
periodSeconds: 30
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
# Deliberately lighter than the chrome sidecar's: the code-server
# image relies on a setuid fixuid helper at startup, which
# no-new-privileges hardening would break. The container is
# password-gated and isolated in its own pod and network.
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: workspace
mountPath: /workspace
- name: code-server-data
mountPath: /home/coder/.local/share/code-server
# Read-only SSH keys so git over SSH works from VS Code terminals.
# The image's coder user is UID 1000, same as the host user the
# hostPath volumes assume.
- name: ssh
mountPath: /home/coder/.ssh
readOnly: true
volumes:
# Same hostPath as the openchamber pod — fine for the single-node
# example this manifest targets; a multi-node cluster needs RWX
# storage for /workspace here instead.
- name: workspace
hostPath:
path: /home/USER/workspace
type: Directory
- name: code-server-data
persistentVolumeClaim:
claimName: code-server-pvc
- name: ssh
hostPath:
path: /home/USER/.ssh
type: Directory
---
apiVersion: v1
kind: Service
metadata:
name: code-server-service
namespace: openchamber
spec:
selector:
app: code-server
ports:
- protocol: TCP
name: http
port: 8080
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: code-server-ingress
namespace: openchamber
annotations:
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-buffering: "off"
spec:
ingressClassName: nginx
rules:
- host: code-server.example.lan
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: code-server-service
port:
number: 8080