From 4534b08a18b4d0091c495c29dcb270d4c0500e28 Mon Sep 17 00:00:00 2001 From: 030 Date: Sun, 15 Jan 2023 16:05:52 +0100 Subject: [PATCH] [#20] Add Helm package. --- .github/workflows/yamllint.yml | 4 +- Dockerfile | 2 +- configs/.yamllint.yaml | 1 + deployments/helm/.helmignore | 23 ++++++ deployments/helm/Chart.yaml | 24 ++++++ deployments/helm/templates/NOTES.txt | 22 ++++++ deployments/helm/templates/_helpers.tpl | 62 +++++++++++++++ deployments/helm/templates/hpa.yaml | 24 ++++++ deployments/helm/templates/ingress.yaml | 24 ++++++ .../helm/templates/persistentvolume.yaml | 13 ++++ .../helm/templates/persistentvolumeclaim.yaml | 13 ++++ deployments/helm/templates/secret.yaml | 18 +++++ deployments/helm/templates/service.yaml | 15 ++++ deployments/helm/templates/statefulset.yaml | 71 ++++++++++++++++++ deployments/helm/values.yaml | 64 ++++++++++++++++ docs/CHANGELOG.md | 11 +-- index.yaml | 15 ++++ yaam-0.1.0.tgz | Bin 0 -> 3262 bytes 18 files changed, 395 insertions(+), 11 deletions(-) create mode 100644 deployments/helm/.helmignore create mode 100644 deployments/helm/Chart.yaml create mode 100644 deployments/helm/templates/NOTES.txt create mode 100644 deployments/helm/templates/_helpers.tpl create mode 100644 deployments/helm/templates/hpa.yaml create mode 100644 deployments/helm/templates/ingress.yaml create mode 100644 deployments/helm/templates/persistentvolume.yaml create mode 100644 deployments/helm/templates/persistentvolumeclaim.yaml create mode 100644 deployments/helm/templates/secret.yaml create mode 100644 deployments/helm/templates/service.yaml create mode 100644 deployments/helm/templates/statefulset.yaml create mode 100644 deployments/helm/values.yaml create mode 100644 index.yaml create mode 100644 yaam-0.1.0.tgz diff --git a/.github/workflows/yamllint.yml b/.github/workflows/yamllint.yml index 118efb8..87cdfdc 100644 --- a/.github/workflows/yamllint.yml +++ b/.github/workflows/yamllint.yml @@ -6,11 +6,9 @@ jobs: runs-on: ubuntu-latest container: image: pipelinecomponents/yamllint:0.22.1 - env: - YAMLLINT_CONFIG_FILE: /code/configs/.yamllint.yaml options: --cpus 1 steps: - name: Checkout code uses: actions/checkout@v2 - name: run yamllint - run: yamllint . + run: yamllint -c configs/.yamllint.yaml . diff --git a/Dockerfile b/Dockerfile index 596ccc5..e945775 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ RUN apk add --no-cache curl=~7 git=~2 && \ chmod +x user.sh && \ ./user.sh -FROM alpine:3.17.0 +FROM alpine:3.17.1 ENV BIN=/usr/local/bin/ ENV USERNAME=yaam ENV BASE=/opt/${USERNAME} diff --git a/configs/.yamllint.yaml b/configs/.yamllint.yaml index 807204c..2c44d8f 100644 --- a/configs/.yamllint.yaml +++ b/configs/.yamllint.yaml @@ -2,4 +2,5 @@ extends: default ignore: | + deployments/helm/ test/npm/demo/node_modules/ diff --git a/deployments/helm/.helmignore b/deployments/helm/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/deployments/helm/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/deployments/helm/Chart.yaml b/deployments/helm/Chart.yaml new file mode 100644 index 0000000..1085601 --- /dev/null +++ b/deployments/helm/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: yaam +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: 'v0.6.0' diff --git a/deployments/helm/templates/NOTES.txt b/deployments/helm/templates/NOTES.txt new file mode 100644 index 0000000..128f772 --- /dev/null +++ b/deployments/helm/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "yaam.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "yaam.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "yaam.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "yaam.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/deployments/helm/templates/_helpers.tpl b/deployments/helm/templates/_helpers.tpl new file mode 100644 index 0000000..68e4226 --- /dev/null +++ b/deployments/helm/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "yaam.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "yaam.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 "yaam.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "yaam.labels" -}} +helm.sh/chart: {{ include "yaam.chart" . }} +{{ include "yaam.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "yaam.selectorLabels" -}} +app.kubernetes.io/name: {{ include "yaam.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "yaam.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "yaam.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/deployments/helm/templates/hpa.yaml b/deployments/helm/templates/hpa.yaml new file mode 100644 index 0000000..ea8813c --- /dev/null +++ b/deployments/helm/templates/hpa.yaml @@ -0,0 +1,24 @@ +{{- if .Values.autoscaling.enabled }} +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: yaam +spec: + behavior: + scaleDown: + stabilizationWindowSeconds: {{ .Values.autoscaling.stabilizationWindowSeconds }} + metrics: + - resource: + name: cpu + target: + averageUtilization: {{ .Values.autoscaling.averageCpuUtilization }} + type: Utilization + type: Resource + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + scaleTargetRef: + apiVersion: apps/v1 + kind: StatefulSet + name: yaam +{{- end }} diff --git a/deployments/helm/templates/ingress.yaml b/deployments/helm/templates/ingress.yaml new file mode 100644 index 0000000..2ad5358 --- /dev/null +++ b/deployments/helm/templates/ingress.yaml @@ -0,0 +1,24 @@ +{{- if .Values.ingress.enabled -}} +{{- $svcPort := .Values.service.port -}} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: yaam +spec: + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - pathType: {{ .pathType }} + path: {{ .path }} + backend: + service: + name: yaam + port: + number: {{ $svcPort }} + {{- end }} + {{- end }} +{{- end }} diff --git a/deployments/helm/templates/persistentvolume.yaml b/deployments/helm/templates/persistentvolume.yaml new file mode 100644 index 0000000..bb03269 --- /dev/null +++ b/deployments/helm/templates/persistentvolume.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: repositories +spec: + storageClassName: standard + accessModes: + - ReadWriteOnce + capacity: + storage: 2Gi + hostPath: + path: /opt/yaam/repositories/ diff --git a/deployments/helm/templates/persistentvolumeclaim.yaml b/deployments/helm/templates/persistentvolumeclaim.yaml new file mode 100644 index 0000000..8f89ffb --- /dev/null +++ b/deployments/helm/templates/persistentvolumeclaim.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: repositories +spec: + volumeName: repositories + storageClassName: standard + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/deployments/helm/templates/secret.yaml b/deployments/helm/templates/secret.yaml new file mode 100644 index 0000000..551328e --- /dev/null +++ b/deployments/helm/templates/secret.yaml @@ -0,0 +1,18 @@ +{{- if .Values.secret.enabled }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: creds +data: + pass: {{ .Values.secret.pass }} + user: {{ .Values.secret.user }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: conf +type: Opaque +stringData: + config.yml: {{- .Values.secret.conf | toYaml | indent 1 }} +{{- end }} diff --git a/deployments/helm/templates/service.yaml b/deployments/helm/templates/service.yaml new file mode 100644 index 0000000..0dba23b --- /dev/null +++ b/deployments/helm/templates/service.yaml @@ -0,0 +1,15 @@ +{{- if .Values.service.enabled }} +--- +apiVersion: v1 +kind: Service +metadata: + name: yaam + labels: + app: yaam +spec: + ports: + - port: {{ .Values.service.port }} + name: yaam + selector: + app: yaam +{{- end }} \ No newline at end of file diff --git a/deployments/helm/templates/statefulset.yaml b/deployments/helm/templates/statefulset.yaml new file mode 100644 index 0000000..4c08449 --- /dev/null +++ b/deployments/helm/templates/statefulset.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: yaam + labels: + app: yaam +spec: + serviceName: yaam + selector: + matchLabels: + app: yaam + replicas: 2 + template: + metadata: + labels: + app: yaam + spec: + containers: + - name: yaam + env: + - name: YAAM_LOG_LEVEL + value: info + - name: YAAM_HOST + value: yaam.some-domain + - name: YAAM_USER + valueFrom: + secretKeyRef: + name: creds + key: user + - name: YAAM_PASS + valueFrom: + secretKeyRef: + name: creds + key: pass + image: '{{ .Values.image.repository }}:{{ default .Chart.AppVersion .Values.image.tag }}' + livenessProbe: + httpGet: + path: /status + port: 25213 + readinessProbe: + httpGet: + path: /status + port: 25213 + resources: + limits: + cpu: 480m + memory: 30Mi + requests: + cpu: 96m + memory: 5Mi + ports: + - containerPort: 25213 + name: yaam + volumeMounts: + - name: conf + mountPath: /opt/yaam/.yaam + - name: repositories + mountPath: /opt/yaam/.yaam/repositories + - mountPath: /opt/yaam/.yaam/logs + name: logs + volumes: + - name: conf + secret: + secretName: conf + - name: repositories + persistentVolumeClaim: + claimName: repositories + - name: logs + emptyDir: + sizeLimit: 50Mi diff --git a/deployments/helm/values.yaml b/deployments/helm/values.yaml new file mode 100644 index 0000000..97f86de --- /dev/null +++ b/deployments/helm/values.yaml @@ -0,0 +1,64 @@ +--- +autoscaling: + averageCpuUtilization: 90 + enabled: true + maxReplicas: 10 + minReplicas: 2 + stabilizationWindowSeconds: 30 + +image: + repository: utrecht/yaam + # By default, the appVersion is set + # tag: v0.5.3 + +ingress: + enabled: true + hosts: + - host: yaam.some-domain + paths: + - path: / + pathType: Prefix + +secret: + conf: | + caches: + apt: + 3rdparty-ubuntu-nl-archive: + url: http://nl.archive.ubuntu.com/ubuntu/ + maven: + 3rdparty-maven: + url: https://repo.maven.apache.org/maven2/ + 3rdparty-maven-gradle-plugins: + url: https://plugins.gradle.org/m2/ + 3rdparty-maven-spring: + url: https://repo.spring.io/release/ + other-nexus-repo-releases: + url: https://some-nexus/repository/some-repo/ + user: some-user + pass: some-pass + npm: + 3rdparty-npm: + url: https://registry.npmjs.org/ + groups: + maven: + hello: + - maven/releases + - maven/3rdparty-maven + - maven/3rdparty-maven-gradle-plugins + - maven/3rdparty-maven-spring + - maven/other-nexus-repo-releases + publications: + generic: + - something + maven: + - releases + npm: + - 3rdparty-npm + enabled: true + pass: d29ybGQ= + user: aGVsbG8= + +service: + enabled: true + port: 25213 + type: ClusterIP diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 80e0856..041b008 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,11 +2,8 @@ ## [Unreleased] - -## [v0.5.4] - 2022-12-27 -### Build -- **deps:** Update versions. - + +## [0.6.0] - 2023-01-15 ## [v0.5.3] - 2022-11-27 @@ -76,8 +73,8 @@ ## v0.2.1 - 2022-08-23 -[Unreleased]: https://github.com/030/yaam/compare/v0.5.4...HEAD -[v0.5.4]: https://github.com/030/yaam/compare/v0.5.3...v0.5.4 +[Unreleased]: https://github.com/030/yaam/compare/0.6.0...HEAD +[0.6.0]: https://github.com/030/yaam/compare/v0.5.3...0.6.0 [v0.5.3]: https://github.com/030/yaam/compare/v0.5.2...v0.5.3 [v0.5.2]: https://github.com/030/yaam/compare/v0.5.1...v0.5.2 [v0.5.1]: https://github.com/030/yaam/compare/v0.5.0...v0.5.1 diff --git a/index.yaml b/index.yaml new file mode 100644 index 0000000..e75a7ca --- /dev/null +++ b/index.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +entries: + yaam: + - apiVersion: v2 + appVersion: v0.5.3 + created: '2023-01-15T15:59:00.281869597+01:00' + description: A Helm chart for Kubernetes + digest: 07600c06e72518c299ce2983ba4aa69efa48cdbd47d639ed8a212ba9be436b95 + name: yaam + type: application + urls: + - https://030.github.io/yaam/yaam-0.1.0.tgz + version: 0.1.0 +generated: '2023-01-15T15:59:00.281363636+01:00' diff --git a/yaam-0.1.0.tgz b/yaam-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..5fa384acc8d372dbfc91eea5c7691255cac0533a GIT binary patch literal 3262 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 literal 0 HcmV?d00001