feat: deploy to Frostmoln production
The AWS deploy-prod job has failed since CI left AWS (no k8s.unbound.se context on the runners). Deploys now go to the Frostmoln production cluster (namespace schemas), the way the sites do. Frostmoln enforces restricted Pod Security: the pod runs as the image's non-root user with a matching securityContext. The prod-only manifests move to the prod-frostmoln target; the ALB ingress becomes a Traefik one with a Let's Encrypt certificate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ma8eHhW59hLAJuLzBnLvUu
This commit is contained in:
1 parent
acf6f8df62
commit
1646ee1bc3
8 files changed
+63
-38
No files matched your search
@@ -18,16 +18,30 @@ jobs:
|
|||||||
- name: Build and push
|
- name: Build and push
|
||||||
run: unset GITEA_TOKEN && build && push
|
run: unset GITEA_TOKEN && build && push
|
||||||
|
|
||||||
deploy-prod:
|
deploy-prod-frostmoln:
|
||||||
needs: build
|
needs: build
|
||||||
if: gitea.ref == 'refs/heads/main'
|
if: gitea.ref == 'refs/heads/main'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
BUILDTOOLS_CONTENT: ${{ secrets.BUILDTOOLS_CONTENT }}
|
BUILDTOOLS_CONTENT: ${{ secrets.BUILDTOOLS_FROSTMOLN_PRODUCTION }}
|
||||||
GITEA_REPOSITORY: ${{ gitea.repository }}
|
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||||
|
FM_TENANT: 9ae61342-0f76-476f-8176-e46f6b0b0c32
|
||||||
environment: prod
|
environment: prod
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
- uses: buildtool/setup-buildtools-action@v1
|
- uses: buildtool/setup-buildtools-action@v1
|
||||||
- name: Deploy to production
|
- name: Install fm
|
||||||
run: deploy prod
|
run: |
|
||||||
|
sudo install -d -m 0755 /etc/apt/keyrings
|
||||||
|
sudo curl -fsSL -o /etc/apt/keyrings/frostmoln.gpg https://get.frostmoln.cloud/deb/frostmoln.gpg
|
||||||
|
sudo curl -fsSL -o /etc/apt/sources.list.d/frostmoln.sources https://get.frostmoln.cloud/deb/frostmoln.sources
|
||||||
|
sudo apt-get update && sudo apt-get install -y fm
|
||||||
|
- name: Deploy to Frostmoln production
|
||||||
|
env:
|
||||||
|
FROSTMOLN_API_KEY: ${{ secrets.FROSTMOLN_PRODUCTION_DEPLOY_KEY }}
|
||||||
|
# The cluster API is private: the kubeconfig's exec plugin opens an fm
|
||||||
|
# tunnel on demand. fm reads the key from its config file, not the env.
|
||||||
|
run: |
|
||||||
|
fm auth api-key set "$FROSTMOLN_API_KEY" > /dev/null
|
||||||
|
fm kubernetes cluster kubeconfig ${{ vars.FROSTMOLN_PRODUCTION_CLUSTER_ID }} --exec --file "$RUNNER_TEMP/kubeconfig"
|
||||||
|
KUBECONFIG_CONTENT="$(cat "$RUNNER_TEMP/kubeconfig")" deploy prod-frostmoln -n schemas
|
||||||
+2
-1
@@ -12,7 +12,8 @@ RUN npm run postinstall && npm run generate && npm run lint && npm run test:cove
|
|||||||
FROM scratch as export
|
FROM scratch as export
|
||||||
COPY --from=builder /build/coverage /
|
COPY --from=builder /build/coverage /
|
||||||
|
|
||||||
FROM amd64/nginx:1.31.7@sha256:227f31dac037a2f1cdbd17ca9eec940a76c6c1b3187f5285eca2c3edb5d19c31
|
# Unprivileged (uid 101, port 8080): Frostmoln enforces restricted Pod Security.
|
||||||
|
FROM nginxinc/nginx-unprivileged:1.31.6@sha256:1d26a516fb93b18f9c0214dc9c645054486e516f13ff09ef63d5bd5c622278a2
|
||||||
MAINTAINER Joakim Olsson <joakim@unbound.se>
|
MAINTAINER Joakim Olsson <joakim@unbound.se>
|
||||||
|
|
||||||
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
|
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
|||||||
File renamed without changes.
+13
-5
@@ -23,6 +23,11 @@ spec:
|
|||||||
app.kubernetes.io/name: schemas-app
|
app.kubernetes.io/name: schemas-app
|
||||||
app.kubernetes.io/instance: schemas
|
app.kubernetes.io/instance: schemas
|
||||||
spec:
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 101
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
affinity:
|
affinity:
|
||||||
podAntiAffinity:
|
podAntiAffinity:
|
||||||
preferredDuringSchedulingIgnoredDuringExecution:
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
@@ -39,19 +44,23 @@ spec:
|
|||||||
- name: schemas-app
|
- name: schemas-app
|
||||||
image: ${IMAGE}
|
image: ${IMAGE}
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop: ["ALL"]
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 8080
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /
|
path: /
|
||||||
port: 80
|
port: 8080
|
||||||
initialDelaySeconds: 5
|
initialDelaySeconds: 5
|
||||||
periodSeconds: 5
|
periodSeconds: 5
|
||||||
timeoutSeconds: 5
|
timeoutSeconds: 5
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /
|
path: /
|
||||||
port: 80
|
port: 8080
|
||||||
initialDelaySeconds: 10
|
initialDelaySeconds: 10
|
||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
timeoutSeconds: 5
|
timeoutSeconds: 5
|
||||||
@@ -71,9 +80,8 @@ metadata:
|
|||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: schemas-app
|
app.kubernetes.io/name: schemas-app
|
||||||
spec:
|
spec:
|
||||||
type: NodePort
|
|
||||||
selector:
|
selector:
|
||||||
app.kubernetes.io/name: schemas-app
|
app.kubernetes.io/name: schemas-app
|
||||||
ports:
|
ports:
|
||||||
- port: 80
|
- port: 80
|
||||||
targetPort: 80
|
targetPort: 8080
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: schemas-app
|
||||||
|
annotations:
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
|
traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
rules:
|
||||||
|
- host: "schemas.unbound.se"
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
# Everything but /query, which the schemas API's longer path takes.
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: schemas-app
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: schemas-app-ingress
|
|
||||||
annotations:
|
|
||||||
alb.ingress.kubernetes.io/group.name: "default"
|
|
||||||
alb.ingress.kubernetes.io/group.order: "15"
|
|
||||||
alb.ingress.kubernetes.io/scheme: internet-facing
|
|
||||||
alb.ingress.kubernetes.io/target-type: instance
|
|
||||||
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80},{"HTTPS": 443}]'
|
|
||||||
alb.ingress.kubernetes.io/ssl-redirect: "443"
|
|
||||||
alb.ingress.kubernetes.io/healthcheck-path: '/'
|
|
||||||
spec:
|
|
||||||
ingressClassName: "alb"
|
|
||||||
rules:
|
|
||||||
- host: "schemas.unbound.se"
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /*
|
|
||||||
pathType: ImplementationSpecific
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: schemas-app
|
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
File renamed without changes.
+9
-3
@@ -1,14 +1,20 @@
|
|||||||
user nginx;
|
|
||||||
worker_processes auto;
|
worker_processes auto;
|
||||||
|
|
||||||
error_log /var/log/nginx/error.log notice;
|
error_log /var/log/nginx/error.log notice;
|
||||||
pid /var/run/nginx.pid;
|
pid /tmp/nginx.pid;
|
||||||
|
|
||||||
events {
|
events {
|
||||||
worker_connections 1024;
|
worker_connections 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
http {
|
http {
|
||||||
|
# Writable by the unprivileged (uid 101) worker.
|
||||||
|
client_body_temp_path /tmp/client_temp;
|
||||||
|
proxy_temp_path /tmp/proxy_temp_path;
|
||||||
|
fastcgi_temp_path /tmp/fastcgi_temp;
|
||||||
|
uwsgi_temp_path /tmp/uwsgi_temp;
|
||||||
|
scgi_temp_path /tmp/scgi_temp;
|
||||||
|
|
||||||
include /etc/nginx/mime.types;
|
include /etc/nginx/mime.types;
|
||||||
default_type application/octet-stream;
|
default_type application/octet-stream;
|
||||||
types {
|
types {
|
||||||
@@ -22,7 +28,7 @@ http {
|
|||||||
keepalive_timeout 65;
|
keepalive_timeout 65;
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 8080;
|
||||||
server_name localhost;
|
server_name localhost;
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html index.htm;
|
index index.html index.htm;
|
||||||
|
|||||||
Reference in new issue
Block a user