feat: add n8n Helm chart for Kubernetes on Raspberry Pi (ARM64)
Helm Chart Release / release-chart (push) Successful in 4s
Helm Chart Release / release-chart (push) Successful in 4s
- n8nio/n8n 2.19.2 (multi-arch, linux/arm64 ready) - Single-container deployment with persistent storage - SQLite default / PostgreSQL option - Basic auth, encryption key via Secret - Ingress, HPA, PDB, NetworkPolicy support - Gitea CI: weekly auto-update + release workflow Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
# .gitea/workflows/helm-release.yaml
|
||||
# 汎用Helmチャート公開ワークフロー(全リポジトリ共通)
|
||||
|
||||
name: Helm Chart Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY_URL: https://git.cafepieters.com
|
||||
OWNER: helmchart
|
||||
|
||||
jobs:
|
||||
release-chart:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
echo "🔄 Cloning repository..."
|
||||
git clone --depth 1 $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git .
|
||||
echo "✅ Repository cloned"
|
||||
|
||||
- name: Install Helm
|
||||
run: |
|
||||
echo "📦 Installing Helm..."
|
||||
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
||||
helm version
|
||||
echo "✅ Helm installed"
|
||||
|
||||
- name: Get Chart Info
|
||||
id: chart_info
|
||||
run: |
|
||||
CHART_NAME=$(grep '^name:' Chart.yaml | awk '{print $2}')
|
||||
CHART_VERSION=$(grep '^version:' Chart.yaml | awk '{print $2}')
|
||||
|
||||
echo "Chart Name: ${CHART_NAME}"
|
||||
echo "Chart Version: ${CHART_VERSION}"
|
||||
|
||||
echo "CHART_NAME=${CHART_NAME}" >> $GITHUB_ENV
|
||||
echo "CHART_VERSION=${CHART_VERSION}" >> $GITHUB_ENV
|
||||
|
||||
- name: Validate Chart
|
||||
run: |
|
||||
echo "🔍 Validating Helm chart..."
|
||||
helm lint .
|
||||
echo "✅ Chart validation passed"
|
||||
|
||||
- name: Package Chart
|
||||
run: |
|
||||
echo "📦 Packaging Helm chart..."
|
||||
helm package .
|
||||
|
||||
CHART_FILE=$(ls *.tgz)
|
||||
echo "✅ Packaged: ${CHART_FILE}"
|
||||
echo "CHART_FILE=${CHART_FILE}" >> $GITHUB_ENV
|
||||
|
||||
- name: Publish to Gitea Package Registry
|
||||
run: |
|
||||
echo "🚀 Publishing ${CHART_FILE} to Gitea Package Registry..."
|
||||
|
||||
curl --fail-with-body \
|
||||
-u "${{ secrets.REGISTRY_USER }}:${{ secrets.REGISTRY_TOKEN }}" \
|
||||
-X POST \
|
||||
--upload-file "${CHART_FILE}" \
|
||||
"${REGISTRY_URL}/api/packages/${OWNER}/helm/api/charts"
|
||||
|
||||
echo "✅ Chart published successfully!"
|
||||
|
||||
- name: Summary
|
||||
if: success()
|
||||
run: |
|
||||
echo "================================"
|
||||
echo "✅ Deployment Successful!"
|
||||
echo "================================"
|
||||
echo "Repository: ${{ github.repository }}"
|
||||
echo "Chart Name: ${CHART_NAME}"
|
||||
echo "Chart Version: ${CHART_VERSION}"
|
||||
echo "Chart File: ${CHART_FILE}"
|
||||
echo "Branch: ${{ github.ref }}"
|
||||
echo "Commit: ${{ github.sha }}"
|
||||
echo "================================"
|
||||
echo ""
|
||||
echo "📦 Install with:"
|
||||
echo "helm repo add cafepieters ${REGISTRY_URL}/api/packages/${OWNER}/helm"
|
||||
echo "helm repo update"
|
||||
echo "helm install my-${CHART_NAME} cafepieters/${CHART_NAME}"
|
||||
|
||||
- name: Error Report
|
||||
if: failure()
|
||||
run: |
|
||||
echo "================================"
|
||||
echo "❌ Deployment Failed!"
|
||||
echo "================================"
|
||||
echo "Check the logs above for details"
|
||||
echo "Common issues:"
|
||||
echo "- Missing Chart.yaml"
|
||||
echo "- Invalid Helm chart structure"
|
||||
echo "- Missing REGISTRY_USER or REGISTRY_TOKEN secrets"
|
||||
echo "- Insufficient permissions on Personal Access Token"
|
||||
echo "================================"
|
||||
@@ -0,0 +1,197 @@
|
||||
name: Update Docker Image Tags and Release Helm Chart
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 3 * * 1' # 毎週月曜日 3:00 AM (JST 12:00 PM)
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY_URL: https://git.cafepieters.com
|
||||
OWNER: helmchart
|
||||
|
||||
jobs:
|
||||
update-and-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Helm
|
||||
uses: azure/setup-helm@v3
|
||||
with:
|
||||
version: 'v3.12.0'
|
||||
|
||||
- name: Check jq availability
|
||||
run: |
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "Installing jq..."
|
||||
apt-get update && apt-get install -y jq
|
||||
fi
|
||||
jq --version
|
||||
|
||||
- name: Check for new n8n version
|
||||
id: n8n
|
||||
run: |
|
||||
set -e
|
||||
echo "Checking n8n versions..."
|
||||
CURRENT=$(grep "tag:" values.yaml | head -1 | sed 's/.*tag: *"\([^"]*\)".*/\1/' | tr -d ' ')
|
||||
echo "Current n8n: $CURRENT"
|
||||
|
||||
LATEST=$(curl -s "https://registry.hub.docker.com/v2/repositories/n8nio/n8n/tags?page_size=100" | \
|
||||
jq -r '.results[].name' | \
|
||||
grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | \
|
||||
sort -V | tail -1)
|
||||
|
||||
if [ -z "$LATEST" ]; then
|
||||
echo "Warning: Could not fetch latest n8n version, using current"
|
||||
LATEST="$CURRENT"
|
||||
fi
|
||||
|
||||
echo "Latest n8n: $LATEST"
|
||||
echo "current=$CURRENT" >> $GITHUB_OUTPUT
|
||||
echo "latest=$LATEST" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Determine update and release conditions
|
||||
id: check_update
|
||||
run: |
|
||||
set -e
|
||||
N8N_CURRENT="${{ steps.n8n.outputs.current }}"
|
||||
N8N_LATEST="${{ steps.n8n.outputs.latest }}"
|
||||
|
||||
echo "n8n: $N8N_CURRENT vs $N8N_LATEST"
|
||||
|
||||
UPDATE_NEEDED=false
|
||||
RELEASE_NEEDED=false
|
||||
|
||||
if [ "$N8N_CURRENT" != "$N8N_LATEST" ]; then
|
||||
UPDATE_NEEDED=true
|
||||
RELEASE_NEEDED=true
|
||||
echo "Update and release needed"
|
||||
else
|
||||
echo "Already up to date"
|
||||
fi
|
||||
|
||||
echo "update_needed=$UPDATE_NEEDED" >> $GITHUB_OUTPUT
|
||||
echo "release_needed=$RELEASE_NEEDED" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update values.yaml
|
||||
if: steps.check_update.outputs.update_needed == 'true'
|
||||
run: |
|
||||
set -e
|
||||
echo "Updating values.yaml..."
|
||||
N8N_OLD="${{ steps.n8n.outputs.current }}"
|
||||
N8N_NEW="${{ steps.n8n.outputs.latest }}"
|
||||
|
||||
sed -i "s/tag: \"${N8N_OLD}\"/tag: \"${N8N_NEW}\"/" values.yaml
|
||||
echo "n8n updated: $N8N_OLD -> $N8N_NEW"
|
||||
|
||||
echo "values.yaml updated"
|
||||
git diff values.yaml
|
||||
|
||||
- name: Update Chart.yaml version
|
||||
if: steps.check_update.outputs.release_needed == 'true'
|
||||
run: |
|
||||
set -e
|
||||
APP_VERSION="${{ steps.n8n.outputs.latest }}"
|
||||
sed -i "s/^version: .*/version: \"$APP_VERSION\"/" Chart.yaml
|
||||
sed -i "s/^appVersion: .*/appVersion: \"$APP_VERSION\"/" Chart.yaml
|
||||
echo "Chart.yaml updated to version $APP_VERSION"
|
||||
cat Chart.yaml
|
||||
|
||||
- name: Commit changes
|
||||
if: steps.check_update.outputs.update_needed == 'true'
|
||||
run: |
|
||||
git config user.name "Claude"
|
||||
git config user.email "claude@cafepieters.com"
|
||||
git add values.yaml Chart.yaml
|
||||
git commit -m "chore: update n8n to ${{ steps.n8n.outputs.latest }}"
|
||||
git push origin main
|
||||
|
||||
- name: Package Helm Chart
|
||||
if: steps.check_update.outputs.release_needed == 'true'
|
||||
run: |
|
||||
helm package .
|
||||
echo "Helm chart packaged"
|
||||
|
||||
- name: Create Git Tag
|
||||
if: steps.check_update.outputs.release_needed == 'true'
|
||||
run: |
|
||||
APP_VERSION="${{ steps.n8n.outputs.latest }}"
|
||||
if git rev-parse "v$APP_VERSION" >/dev/null 2>&1; then
|
||||
echo "Tag v$APP_VERSION already exists, skipping"
|
||||
else
|
||||
git tag -a "v$APP_VERSION" -m "Release n8n $APP_VERSION"
|
||||
git push origin "v$APP_VERSION"
|
||||
echo "Git tag v$APP_VERSION created"
|
||||
fi
|
||||
|
||||
- name: Create Gitea Release
|
||||
if: steps.check_update.outputs.release_needed == 'true'
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
run: |
|
||||
APP_VERSION="${{ steps.n8n.outputs.latest }}"
|
||||
CHART_NAME=$(grep '^name:' Chart.yaml | awk '{print $2}')
|
||||
PACKAGE_FILE="${CHART_NAME}-${APP_VERSION}.tgz"
|
||||
RELEASE_BODY="n8n Helm Chart v${APP_VERSION} - n8n: ${{ steps.n8n.outputs.latest }}"
|
||||
|
||||
EXISTING=$(curl -s \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/v${APP_VERSION}" | jq -r '.id // empty')
|
||||
|
||||
if [ -n "$EXISTING" ]; then
|
||||
echo "Release v$APP_VERSION already exists (id=$EXISTING), skipping"
|
||||
else
|
||||
RELEASE_ID=$(curl -s -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"v${APP_VERSION}\",\"name\":\"v${APP_VERSION}\",\"body\":\"${RELEASE_BODY}\"}" \
|
||||
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases" | jq -r '.id')
|
||||
|
||||
curl -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/gzip" \
|
||||
--data-binary "@${PACKAGE_FILE}" \
|
||||
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets?name=${PACKAGE_FILE}"
|
||||
|
||||
echo "Release v$APP_VERSION created with asset: ${PACKAGE_FILE}"
|
||||
fi
|
||||
|
||||
- name: Publish to Gitea Package Registry
|
||||
if: steps.check_update.outputs.release_needed == 'true'
|
||||
run: |
|
||||
CHART_NAME=$(grep '^name:' Chart.yaml | awk '{print $2}')
|
||||
APP_VERSION="${{ steps.n8n.outputs.latest }}"
|
||||
PACKAGE_FILE="${CHART_NAME}-${APP_VERSION}.tgz"
|
||||
|
||||
echo "Publishing ${PACKAGE_FILE} to Gitea Package Registry..."
|
||||
curl --fail-with-body \
|
||||
-u "${{ secrets.REGISTRY_USER }}:${{ secrets.REGISTRY_TOKEN }}" \
|
||||
-X POST \
|
||||
--upload-file "${PACKAGE_FILE}" \
|
||||
"${REGISTRY_URL}/api/packages/${OWNER}/helm/api/charts"
|
||||
|
||||
echo "Chart published to registry successfully"
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
APP_VERSION="${{ steps.n8n.outputs.latest }}"
|
||||
UPDATE_NEEDED="${{ steps.check_update.outputs.update_needed }}"
|
||||
RELEASE_NEEDED="${{ steps.check_update.outputs.release_needed }}"
|
||||
echo "========================================"
|
||||
if [ "$UPDATE_NEEDED" = "true" ]; then
|
||||
echo "Update completed!"
|
||||
else
|
||||
echo "Already up to date, no changes."
|
||||
fi
|
||||
echo "========================================"
|
||||
echo "n8n: ${APP_VERSION}"
|
||||
if [ "$RELEASE_NEEDED" = "true" ]; then
|
||||
echo "Chart Version: ${APP_VERSION} (released)"
|
||||
echo "Registry: ${REGISTRY_URL}/api/packages/${OWNER}/helm"
|
||||
else
|
||||
echo "No release needed"
|
||||
fi
|
||||
echo "========================================"
|
||||
@@ -0,0 +1,73 @@
|
||||
# n8n Helm Chart - CLAUDE.md
|
||||
|
||||
## リポジトリ概要
|
||||
|
||||
n8n ワークフロー自動化ツールを Kubernetes 上にデプロイするHelmチャートです。
|
||||
Raspberry Pi などのベアメタル上で動作する Kubernetes クラスタを想定した構成になっています。
|
||||
`n8nio/n8n` 公式マルチアーキテクチャイメージ(linux/arm64 対応)を使用します。
|
||||
|
||||
## ARM 対応について
|
||||
|
||||
`n8nio/n8n` は linux/arm64 に対応したマルチアーキテクチャイメージです。
|
||||
Raspberry Pi 4 以降(arm64)で動作します。
|
||||
|
||||
## Git 情報
|
||||
|
||||
- **ユーザー名**: Claude
|
||||
- **メールアドレス**: claude@cafepieters.com
|
||||
- **リポジトリ**: ssh://git@192.168.9.65/helmchart/n8n.git
|
||||
|
||||
## チャート構成
|
||||
|
||||
| リソース | 説明 |
|
||||
|---|---|
|
||||
| Deployment | n8n 本体(シングルコンテナ) |
|
||||
| Service | LoadBalancer / ClusterIP(ポート 5678) |
|
||||
| PVC | n8n データ(ワークフロー・認証情報・SQLite DB)永続化 |
|
||||
| Secret | 暗号化キー・Basic認証パスワード・DBパスワード |
|
||||
| Ingress | オプション(nginx ingress controller 対応) |
|
||||
| HPA | オプション(※SQLiteモード時はスケールアウト非推奨) |
|
||||
| PDB | Pod Disruption Budget |
|
||||
| NetworkPolicy | オプション |
|
||||
|
||||
## データ永続化
|
||||
|
||||
n8n のデータは `/home/node/.n8n` に保存されます。
|
||||
`persistence.enabled: true`(デフォルト)で PVC に永続化されます。
|
||||
**persistence.enabled: false の場合、Pod 再起動でデータが失われます。**
|
||||
|
||||
## データベース
|
||||
|
||||
- デフォルト: SQLite(`/home/node/.n8n/database.sqlite`)
|
||||
- 本番推奨: PostgreSQL(`n8n.database.type: postgresdb`)
|
||||
|
||||
## リリースフローのルール
|
||||
|
||||
### バージョン番号の方針
|
||||
- Helmチャートのバージョン番号(`Chart.yaml` の `version` / `appVersion`)は、**n8n のバージョン番号と同一**とする。
|
||||
|
||||
### 自動リリース条件
|
||||
- **n8n バージョン更新時**: `values.yaml` と `Chart.yaml` を更新し、Gitタグ・Giteaリリース・Gitea Package Registry への発行まで行う。
|
||||
|
||||
### 手動リリース(臨時)
|
||||
- 改修作業などで手動リリースが必要な場合は、バージョン末尾にアルファベットを付与する。
|
||||
- 例: `2.19.2` → `2.19.2-a`, `2.19.2-b`
|
||||
|
||||
## ワークフロー構成
|
||||
|
||||
### `.gitea/workflows/image-update-and-release.yaml`
|
||||
毎週月曜日 3:00 AM(JST 12:00 PM)に自動実行され、以下を行う:
|
||||
1. Docker Hub から n8n の最新バージョンを取得
|
||||
2. 更新がある場合は `values.yaml` と `Chart.yaml` を更新
|
||||
3. Gitタグ・Giteaリリース・Gitea Package Registry への発行を実施
|
||||
|
||||
### `.gitea/workflows/helm-release.yaml`
|
||||
`main` ブランチへのプッシュ時に自動実行。Gitea Package Registry にチャートを発行する。
|
||||
|
||||
## 必要な Gitea Secrets
|
||||
|
||||
| シークレット名 | 用途 |
|
||||
|---|---|
|
||||
| `GITEA_TOKEN` | Gitea API(リリース作成・タグ操作) |
|
||||
| `REGISTRY_USER` | Gitea Package Registry ユーザー名 |
|
||||
| `REGISTRY_TOKEN` | Gitea Package Registry トークン |
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
apiVersion: v2
|
||||
name: n8n
|
||||
description: A Helm chart for n8n workflow automation on Kubernetes (ARM/Raspberry Pi ready)
|
||||
type: application
|
||||
version: "2.19.2"
|
||||
appVersion: "2.19.2"
|
||||
keywords:
|
||||
- n8n
|
||||
- workflow
|
||||
- automation
|
||||
- integration
|
||||
maintainers:
|
||||
- name: Pieter
|
||||
url: https://git.cafepieters.com/helmchart/repo/
|
||||
home: https://n8n.io/
|
||||
sources:
|
||||
- https://github.com/n8n-io/n8n
|
||||
icon: https://n8n.io/favicon.ico
|
||||
kubeVersion: ">=1.19.0-0"
|
||||
annotations:
|
||||
category: Automation
|
||||
licenses: Sustainable Use License
|
||||
@@ -0,0 +1,74 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "n8n.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
*/}}
|
||||
{{- define "n8n.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "n8n.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "n8n.labels" -}}
|
||||
helm.sh/chart: {{ include "n8n.chart" . }}
|
||||
{{ include "n8n.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "n8n.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "n8n.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "n8n.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "n8n.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Name of the secret holding the encryption key and auth credentials
|
||||
*/}}
|
||||
{{- define "n8n.secretName" -}}
|
||||
{{- .Values.n8n.existingSecret | default (include "n8n.fullname" .) }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Name of the PVC for n8n data
|
||||
*/}}
|
||||
{{- define "n8n.pvcName" -}}
|
||||
{{- .Values.persistence.existingClaim | default (include "n8n.fullname" .) }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,149 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "n8n.fullname" . }}
|
||||
labels:
|
||||
{{- include "n8n.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaling.enabled }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "n8n.selectorLabels" . | nindent 6 }}
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
|
||||
{{- with .Values.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "n8n.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "n8n.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: n8n
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 10 }}
|
||||
image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 5678
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: N8N_PORT
|
||||
value: "5678"
|
||||
- name: N8N_PROTOCOL
|
||||
value: {{ .Values.n8n.protocol | quote }}
|
||||
- name: N8N_HOST
|
||||
value: {{ .Values.n8n.host | quote }}
|
||||
{{- if .Values.n8n.webhookUrl }}
|
||||
- name: WEBHOOK_URL
|
||||
value: {{ .Values.n8n.webhookUrl | quote }}
|
||||
{{- end }}
|
||||
- name: GENERIC_TIMEZONE
|
||||
value: {{ .Values.n8n.timezone | quote }}
|
||||
- name: N8N_LOG_LEVEL
|
||||
value: {{ .Values.n8n.logLevel | quote }}
|
||||
- name: N8N_USER_FOLDER
|
||||
value: "/home/node/.n8n"
|
||||
- name: N8N_ENCRYPTION_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "n8n.secretName" . }}
|
||||
key: encryption-key
|
||||
{{- if .Values.n8n.basicAuth.enabled }}
|
||||
- name: N8N_BASIC_AUTH_ACTIVE
|
||||
value: "true"
|
||||
- name: N8N_BASIC_AUTH_USER
|
||||
value: {{ .Values.n8n.basicAuth.user | quote }}
|
||||
- name: N8N_BASIC_AUTH_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .Values.n8n.basicAuth.existingSecret | default (include "n8n.fullname" .) }}
|
||||
key: {{ .Values.n8n.basicAuth.passwordKey }}
|
||||
{{- end }}
|
||||
- name: EXECUTIONS_DATA_PRUNE
|
||||
value: {{ .Values.n8n.executions.pruneData | quote }}
|
||||
- name: EXECUTIONS_DATA_MAX_AGE
|
||||
value: {{ .Values.n8n.executions.pruneDataMaxAge | quote }}
|
||||
- name: EXECUTIONS_DATA_MAX_COUNT
|
||||
value: {{ .Values.n8n.executions.pruneDataMaxCount | quote }}
|
||||
- name: DB_TYPE
|
||||
value: {{ .Values.n8n.database.type | quote }}
|
||||
{{- if eq .Values.n8n.database.type "postgresdb" }}
|
||||
- name: DB_POSTGRESDB_HOST
|
||||
value: {{ .Values.n8n.database.postgresdb.host | quote }}
|
||||
- name: DB_POSTGRESDB_PORT
|
||||
value: {{ .Values.n8n.database.postgresdb.port | quote }}
|
||||
- name: DB_POSTGRESDB_DATABASE
|
||||
value: {{ .Values.n8n.database.postgresdb.database | quote }}
|
||||
- name: DB_POSTGRESDB_USER
|
||||
value: {{ .Values.n8n.database.postgresdb.user | quote }}
|
||||
- name: DB_POSTGRESDB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .Values.n8n.database.postgresdb.existingSecret | default (include "n8n.fullname" .) }}
|
||||
key: {{ .Values.n8n.database.postgresdb.passwordKey }}
|
||||
{{- end }}
|
||||
{{- range $key, $val := .Values.n8n.extraEnv }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $val | quote }}
|
||||
{{- end }}
|
||||
{{- if .Values.livenessProbe.enabled }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: http
|
||||
initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
|
||||
failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
|
||||
successThreshold: {{ .Values.livenessProbe.successThreshold }}
|
||||
{{- end }}
|
||||
{{- if .Values.readinessProbe.enabled }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: http
|
||||
initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
|
||||
failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
|
||||
successThreshold: {{ .Values.readinessProbe.successThreshold }}
|
||||
{{- end }}
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 10 }}
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /home/node/.n8n
|
||||
volumes:
|
||||
- name: data
|
||||
{{- if .Values.persistence.enabled }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "n8n.pvcName" . }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,32 @@
|
||||
{{- if .Values.autoscaling.enabled }}
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "n8n.fullname" . }}
|
||||
labels:
|
||||
{{- include "n8n.labels" . | nindent 4 }}
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "n8n.fullname" . }}
|
||||
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||
metrics:
|
||||
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,35 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ include "n8n.fullname" . }}
|
||||
labels:
|
||||
{{- include "n8n.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.ingress.className }}
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- toYaml .Values.ingress.tls | nindent 4 }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
pathType: {{ .pathType }}
|
||||
backend:
|
||||
service:
|
||||
name: {{ include "n8n.fullname" $ }}
|
||||
port:
|
||||
number: {{ $.Values.service.port }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,22 @@
|
||||
{{- if .Values.networkPolicy.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: {{ include "n8n.fullname" . }}
|
||||
labels:
|
||||
{{- include "n8n.labels" . | nindent 4 }}
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
{{- include "n8n.selectorLabels" . | nindent 6 }}
|
||||
policyTypes:
|
||||
{{- toYaml .Values.networkPolicy.policyTypes | nindent 4 }}
|
||||
{{- if .Values.networkPolicy.ingress }}
|
||||
ingress:
|
||||
{{- toYaml .Values.networkPolicy.ingress | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- if .Values.networkPolicy.egress }}
|
||||
egress:
|
||||
{{- toYaml .Values.networkPolicy.egress | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,18 @@
|
||||
{{- if .Values.podDisruptionBudget.enabled }}
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: {{ include "n8n.fullname" . }}
|
||||
labels:
|
||||
{{- include "n8n.labels" . | nindent 4 }}
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "n8n.selectorLabels" . | nindent 6 }}
|
||||
{{- if .Values.podDisruptionBudget.minAvailable }}
|
||||
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
|
||||
{{- end }}
|
||||
{{- if .Values.podDisruptionBudget.maxUnavailable }}
|
||||
maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,21 @@
|
||||
{{- if and .Values.persistence.enabled (not (.Values.persistence.existingClaim)) -}}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "n8n.fullname" . }}
|
||||
labels:
|
||||
{{- include "n8n.labels" . | nindent 4 }}
|
||||
{{- with .Values.persistence.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
accessModes:
|
||||
- {{ .Values.persistence.accessMode }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.size }}
|
||||
{{- if .Values.persistence.storageClass }}
|
||||
storageClassName: {{ .Values.persistence.storageClass }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,19 @@
|
||||
{{- if not .Values.n8n.existingSecret -}}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "n8n.fullname" . }}
|
||||
labels:
|
||||
{{- include "n8n.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
type: Opaque
|
||||
data:
|
||||
encryption-key: {{ .Values.n8n.encryptionKey | default (randAlphaNum 32) | b64enc | quote }}
|
||||
{{- if and .Values.n8n.basicAuth.enabled (not .Values.n8n.basicAuth.existingSecret) }}
|
||||
basic-auth-password: {{ .Values.n8n.basicAuth.password | default (randAlphaNum 16) | b64enc | quote }}
|
||||
{{- end }}
|
||||
{{- if and (eq .Values.n8n.database.type "postgresdb") (not .Values.n8n.database.postgresdb.existingSecret) }}
|
||||
postgres-password: {{ .Values.n8n.database.postgresdb.password | b64enc | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,19 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "n8n.fullname" . }}
|
||||
labels:
|
||||
{{- include "n8n.labels" . | nindent 4 }}
|
||||
{{- with .Values.service.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: {{ .Values.service.targetPort }}
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "n8n.selectorLabels" . | nindent 4 }}
|
||||
@@ -0,0 +1,12 @@
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "n8n.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "n8n.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
# Default values for n8n
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
registry: docker.io
|
||||
repository: n8nio/n8n
|
||||
tag: "2.19.2"
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
annotations: {}
|
||||
name: ""
|
||||
|
||||
podAnnotations: {}
|
||||
|
||||
podSecurityContext:
|
||||
fsGroup: 1000
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
readOnlyRootFilesystem: false
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
|
||||
service:
|
||||
type: LoadBalancer
|
||||
# type: ClusterIP
|
||||
port: 5678
|
||||
targetPort: 5678
|
||||
annotations: {}
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
className: "nginx"
|
||||
annotations: {}
|
||||
# {
|
||||
# acme.cert-manager.io/http01-ingress-class: "nginx",
|
||||
# cert-manager.io/cluster-issuer: "letsencrypt-issuer",
|
||||
# nginx.ingress.kubernetes.io/from-to-www-redirect: "true",
|
||||
# nginx.ingress.kubernetes.io/proxy-body-size: "100m"
|
||||
# }
|
||||
hosts:
|
||||
- host: n8n.local
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls: []
|
||||
# - secretName: n8n-tls
|
||||
# hosts:
|
||||
# - n8n.local
|
||||
|
||||
# Resource limits suitable for Raspberry Pi
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 256Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 1
|
||||
maxReplicas: 3
|
||||
targetCPUUtilizationPercentage: 80
|
||||
targetMemoryUtilizationPercentage: 80
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchExpressions:
|
||||
- key: app.kubernetes.io/name
|
||||
operator: In
|
||||
values:
|
||||
- n8n
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
# n8n specific configuration
|
||||
n8n:
|
||||
# Encryption key for stored credentials (auto-generated if not provided)
|
||||
encryptionKey: ""
|
||||
existingSecret: ""
|
||||
|
||||
# Host and protocol settings (used for webhook URLs)
|
||||
host: "n8n.local"
|
||||
protocol: "http"
|
||||
# webhookUrl: "https://n8n.example.com/"
|
||||
|
||||
# Timezone
|
||||
timezone: "Asia/Tokyo"
|
||||
|
||||
# Log level: error, warn, info, verbose, debug
|
||||
logLevel: "info"
|
||||
|
||||
# Basic authentication
|
||||
basicAuth:
|
||||
enabled: false
|
||||
user: "admin"
|
||||
password: ""
|
||||
existingSecret: ""
|
||||
passwordKey: "basic-auth-password"
|
||||
|
||||
# Execution data pruning
|
||||
executions:
|
||||
pruneData: true
|
||||
pruneDataMaxAge: 336 # hours (14 days)
|
||||
pruneDataMaxCount: 10000
|
||||
|
||||
# Database configuration
|
||||
database:
|
||||
# type: sqlite (default) or postgresdb
|
||||
type: "sqlite"
|
||||
# PostgreSQL settings (used when type=postgresdb)
|
||||
postgresdb:
|
||||
host: "postgres.default.svc.cluster.local"
|
||||
port: 5432
|
||||
database: "n8n"
|
||||
user: ""
|
||||
password: ""
|
||||
existingSecret: ""
|
||||
passwordKey: "postgres-password"
|
||||
|
||||
# Extra environment variables
|
||||
extraEnv: {}
|
||||
# extraEnv:
|
||||
# N8N_METRICS: "true"
|
||||
# N8N_DIAGNOSTICS_ENABLED: "false"
|
||||
|
||||
# Persistent storage for n8n data (workflows, credentials, sqlite DB)
|
||||
persistence:
|
||||
enabled: true
|
||||
storageClass: ""
|
||||
accessMode: ReadWriteOnce
|
||||
size: 5Gi
|
||||
annotations: {}
|
||||
# existingClaim: ""
|
||||
|
||||
# Liveness and readiness probes
|
||||
livenessProbe:
|
||||
enabled: true
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 6
|
||||
successThreshold: 1
|
||||
|
||||
readinessProbe:
|
||||
enabled: true
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
successThreshold: 1
|
||||
|
||||
# Network Policy
|
||||
networkPolicy:
|
||||
enabled: false
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: ingress-nginx
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5678
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector: {}
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 443
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: kube-system
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
|
||||
# Pod Disruption Budget
|
||||
podDisruptionBudget:
|
||||
enabled: true
|
||||
minAvailable: 1
|
||||
Reference in New Issue
Block a user