sure: new chart for the we-promise Sure app (1.0.0+up0.7.4) #36
No files matched your search
@@ -336,6 +336,13 @@ probe on "/" reach the database at all (see values.yaml).
|
||||
value: "true"
|
||||
- name: RAILS_ENV
|
||||
value: "production"
|
||||
# Not cosmetic and not optional: /tmp is an emptyDir, which Kubernetes creates
|
||||
# mode 0777 without a sticky bit, and Ruby skips exactly that shape when it
|
||||
# looks for a temporary directory. Without this the app aborts during boot with
|
||||
# "could not find a temporary directory". The directory is created by the
|
||||
# init container in the StatefulSet, which explains the whole case.
|
||||
- name: TMPDIR
|
||||
value: "/tmp/ruby"
|
||||
- name: PORT
|
||||
value: "3000"
|
||||
# Sure's production.rb defaults BOTH of these to true. The chart renders them
|
||||
|
||||
@@ -34,6 +34,71 @@ spec:
|
||||
automountServiceAccountToken: false
|
||||
securityContext:
|
||||
{{- include "sure.podSecurityContext" .Values.sure.securityContext | nindent 8 }}
|
||||
initContainers:
|
||||
# Ruby refuses a world-writable directory that has no sticky bit as its
|
||||
# temporary directory (ruby 3.4, tmpdir.rb: a candidate is skipped when
|
||||
# "world_writable? && !sticky?"), and a Kubernetes emptyDir is created
|
||||
# mode 0777 WITHOUT the sticky bit. With /tmp mounted as an emptyDir and
|
||||
# the image's root filesystem read-only, Ruby then finds no candidate at
|
||||
# all and both containers die during boot - measured in this chart's
|
||||
# first install-test, which logged
|
||||
#
|
||||
# system temporary path is world-writable: /tmp
|
||||
# /tmp is world-writable: /tmp
|
||||
# . is not writable: /rails
|
||||
# ArgumentError: could not find a temporary directory
|
||||
#
|
||||
# raised from MiniMagick, which Active Storage loads while Rails boots.
|
||||
# Note where it does NOT come from: nothing in the app asked for a temp
|
||||
# file yet.
|
||||
#
|
||||
# The fix is a directory that is writable without being world-writable.
|
||||
# This init container creates one per app container inside the same
|
||||
# emptyDirs (mode 0700, owned by the pod's uid) and both containers get
|
||||
# TMPDIR pointed at it. It runs as the same non-root uid as the app and
|
||||
# needs no capability at all: the emptyDir root is 0777, so creating an
|
||||
# entry in it is allowed, and chmod on a directory one has just created
|
||||
# needs no privilege. That is deliberately weaker than the chown-as-root
|
||||
# init container paperless-ngx needs.
|
||||
#
|
||||
# The image is the app's own, so there is no second image to pin, no
|
||||
# Renovate block to maintain for it and nothing extra to pull.
|
||||
- name: {{ .Release.Name }}-{{ .Chart.Name }}-init-tmp
|
||||
image: "ghcr.io/we-promise/sure:{{ .Chart.AppVersion }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
# command, not args: the image entrypoint would start the application.
|
||||
command:
|
||||
- /bin/bash
|
||||
- -ec
|
||||
- |
|
||||
for dir in /web-tmp/ruby /worker-tmp/ruby; do
|
||||
mkdir -p "$dir"
|
||||
chmod 700 "$dir"
|
||||
done
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
volumeMounts:
|
||||
- name: web-tmp
|
||||
mountPath: /web-tmp
|
||||
- name: worker-tmp
|
||||
mountPath: /worker-tmp
|
||||
# Fixed, deliberately small: this container runs two mkdirs and two
|
||||
# chmods. Not values-configurable, for the same reason as the init
|
||||
# container in paperless-ngx - there is nothing here to tune.
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 32Mi
|
||||
ephemeral-storage: 1Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 64Mi
|
||||
ephemeral-storage: 10Mi
|
||||
containers:
|
||||
# The web process. Its command is the image's default
|
||||
# (CMD ["./bin/rails", "server"]), which is exactly what makes the
|
||||
@@ -74,8 +139,11 @@ spec:
|
||||
# between the two processes, and a shared scratch directory would
|
||||
# only create coupling nobody asked for.
|
||||
#
|
||||
# /tmp: Rack writes multipart uploads (CSV imports, attachments) to
|
||||
# Dir.tmpdir before Active Storage ever sees them.
|
||||
# /tmp: Rack writes multipart uploads (CSV imports, attachments)
|
||||
# here before Active Storage ever sees them. The app does not use
|
||||
# this path directly, though - TMPDIR points at the private
|
||||
# subdirectory the init container creates inside it, because Ruby
|
||||
# rejects a world-writable emptyDir as a temporary directory.
|
||||
- name: web-tmp
|
||||
mountPath: /tmp
|
||||
# /rails/tmp: Rails' own scratch directory - the Bootsnap cache,
|
||||
|
||||
+12
-4
@@ -36,10 +36,18 @@ sure:
|
||||
# need a ReadWriteMany volume. Above 1 the extra pods normally stay Pending on
|
||||
# "Multi-Attach error for volume" - see NOTES.txt.
|
||||
replicas: 1
|
||||
# Pod-level hardening, shared by both containers. The upstream image declares
|
||||
# USER 1000:1000 (group and user "rails", created in the Dockerfile), so the
|
||||
# chart needs no deviation from the repo hardening standard; fsGroup 1000
|
||||
# makes the storage PVC writable for that uid.
|
||||
# Pod-level hardening, shared by both containers and by the init container.
|
||||
# The upstream image declares USER 1000:1000 (group and user "rails", created
|
||||
# in the Dockerfile), so the chart needs no deviation from the repo hardening
|
||||
# standard; fsGroup 1000 makes the storage PVC writable for that uid.
|
||||
#
|
||||
# readOnlyRootFilesystem: true holds, and the writable paths are emptyDirs:
|
||||
# /rails/tmp, /rails/log and /tmp, one set per container. /tmp needs one extra
|
||||
# step that is easy to mistake for decoration - a tiny init container creates
|
||||
# a private subdirectory in it and the containers run with TMPDIR pointing
|
||||
# there, because Ruby refuses a world-writable directory without a sticky bit
|
||||
# as its temporary directory and an emptyDir is exactly that. Without it the
|
||||
# app aborts during boot; the StatefulSet carries the measured evidence.
|
||||
securityContext:
|
||||
pod:
|
||||
runAsNonRoot: true
|
||||
|
||||
Reference in new issue
Block a user