diff --git a/labs/lab10/k8s/HELM.md b/labs/lab10/k8s/HELM.md new file mode 100644 index 0000000000..d460b20a21 --- /dev/null +++ b/labs/lab10/k8s/HELM.md @@ -0,0 +1,341 @@ +# Lab 10 — Helm Package Manager + +## Chart Overview + +This lab converts the Kubernetes manifests from Lab 09 into a reusable Helm chart for the `devops-info-service` application. + +The Helm chart packages the Kubernetes Deployment and Service into reusable templates with configurable values for different environments. + +Chart location: + +labs/lab10/k8s/devops-info-service/ + + +Chart structure: + +devops-info-service/ +├── Chart.yaml +├── values.yaml +├── values-dev.yaml +├── values-prod.yaml +└── templates/ +├── deployment.yaml +├── service.yaml +├── _helpers.tpl +├── NOTES.txt +└── hooks/ +├── pre-install-job.yaml +└── post-install-job.yaml + + +### Template files + +**deployment.yaml** +Defines the Kubernetes Deployment for the FastAPI application. +Uses values for replicas, image, resources, probes, and security context. + +**service.yaml** +Defines the Kubernetes Service that exposes the application. +Service type and ports are configurable through values. + +**_helpers.tpl** +Contains reusable Helm helper templates for: +- resource naming +- labels +- selectors + +This avoids duplication and follows DRY principles. + +**values.yaml** +Default configuration for the chart. + +**values-dev.yaml** +Overrides for development environment. + +**values-prod.yaml** +Overrides for production environment. + +**hooks/** +Contains Helm lifecycle hook Jobs. + +--- + +## Configuration Guide + +The chart is configurable through Helm values. + +### Replica configuration + +replicaCount + +Controls the number of application Pods. + +### Image configuration + +image.repository +image.tag +image.pullPolicy + + +Defines the Docker image used for the Deployment. + +### Service configuration + +service.type +service.port +service.targetPort +service.nodePort + + +Controls how the application is exposed. + +### Resource configuration + +resources.requests +resources.limits + + +Defines CPU and memory allocation for the container. + +### Health checks + +livenessProbe +readinessProbe + + +Both probes use the `/health` endpoint of the FastAPI application. + +### Security configuration + +securityContext.runAsNonRoot +securityContext.runAsUser +securityContext.allowPrivilegeEscalation + + +Ensures the container runs as a non-root user. + +--- + +## Multi-Environment Configuration + +Two environment configurations were created. + +### Development environment (values-dev.yaml) +- replicaCount: 1 +- Service type: NodePort +- lower resource limits +- faster probe timings +- suitable for local kind cluster + +Install dev environment: + +helm install dev-release . -f values-dev.yaml + + +### Production environment (values-prod.yaml) +- replicaCount: 3 +- Service type: LoadBalancer +- higher resource limits +- production probe timings + +Upgrade to production configuration: + +helm upgrade dev-release devops-info-service -f values-prod.yaml + + +--- + +## Hook Implementation + +Two Helm hooks were implemented. + +### Pre-install Hook + +File: + +templates/hooks/pre-install-job.yaml + + +Purpose: +Runs a validation job before installing the application. + +Hook annotations: + +helm.sh/hook: pre-install +helm.sh/hook-weight: -5 +helm.sh/hook-delete-policy: hook-succeeded + + +### Post-install Hook + +File: + +templates/hooks/post-install-job.yaml + + +Purpose: +Runs a smoke test job after installation. + +Hook annotations: + +helm.sh/hook: post-install +helm.sh/hook-weight: 5 +helm.sh/hook-delete-policy: hook-succeeded + + +### Hook execution order + +1. Pre-install hook runs first +2. Kubernetes resources are installed +3. Post-install hook runs after installation +4. Hook Jobs are deleted after successful execution + +--- + +## Installation Evidence + +### Helm installation + +helm version + + +### Repository exploration + +helm repo add prometheus-community https://prometheus-community.github.io/helm-charts + +helm repo update +helm search repo prometheus +helm show chart prometheus-community/prometheus +helm show values prometheus-community/prometheus + + +### Chart validation + +helm lint . +helm template mychart . +helm install --dry-run --debug test-release . + + +### Install release + +helm install dev-release . -f values-dev.yaml + + +### Verify resources + +helm list +kubectl get pods +kubectl get svc +kubectl get deployment + + +### Application test + +kubectl port-forward service/dev-release-devops-info-service 8080:80 +curl http://localhost:8080 + +curl http://localhost:8080/health + + +Both endpoints returned successful responses. + +--- + +## Operations + +### Install + +helm install dev-release . -f values-dev.yaml + + +### Upgrade to production + +helm upgrade dev-release . -f values-prod.yaml + + +### Release history + +helm history dev-release + + +### Rollback + +helm rollback dev-release 1 + + +### Uninstall + +helm uninstall dev-release + + +--- + +## Testing & Validation + +The chart was validated using: + +### Lint + +helm lint . + + +### Template rendering + +helm template . + + +### Dry-run installation + +helm install --dry-run --debug test-release . + + +### Runtime validation + +kubectl get pods +kubectl get svc +kubectl port-forward +curl / +curl /health + + +All tests passed successfully. + +--- + +## Challenges & Solutions + +### ImagePullBackOff +The kind cluster had intermittent connectivity issues to Docker Hub. + +Solution: +Retried deployment and verified image availability. + +### Security Context Issue +Kubernetes could not verify non-root execution because the image used a named user. + +Solution: +Added numeric UID: + +runAsNonRoot: true +runAsUser: 1000 + + +### Default Helm Templates +The initial chart included unnecessary templates such as httproute and ingress. + +Solution: +Removed unused templates and kept only required resources. + +--- + +## What I Learned + +In this lab I learned: + +- how Helm packages Kubernetes applications into reusable charts +- how to convert static manifests into Helm templates +- how to use values.yaml for configuration +- how to manage multiple environments using values files +- how Helm hooks work +- how to install, upgrade, rollback, and uninstall Helm releases +- how Helm simplifies Kubernetes application management diff --git a/labs/lab10/k8s/devops-info-service/.helmignore b/labs/lab10/k8s/devops-info-service/.helmignore new file mode 100644 index 0000000000..0e8a0eb36f --- /dev/null +++ b/labs/lab10/k8s/devops-info-service/.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/labs/lab10/k8s/devops-info-service/Chart.yaml b/labs/lab10/k8s/devops-info-service/Chart.yaml new file mode 100644 index 0000000000..ad4efa474e --- /dev/null +++ b/labs/lab10/k8s/devops-info-service/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: devops-info-service +description: Helm chart for the DevOps course FastAPI info service +type: application +version: 0.1.0 +appVersion: "1.0.0" + +keywords: + - fastapi + - python + - kubernetes + - helm + +maintainers: + - name: Fayzullin + +sources: + - https://github.com/inno-devops-labs/DevOps-Core-Course diff --git a/labs/lab10/k8s/devops-info-service/templates/NOTES.txt b/labs/lab10/k8s/devops-info-service/templates/NOTES.txt new file mode 100644 index 0000000000..cfd6d4d3bb --- /dev/null +++ b/labs/lab10/k8s/devops-info-service/templates/NOTES.txt @@ -0,0 +1,10 @@ +1. Get the application URL by running these commands: + +{{- if eq .Values.service.type "NodePort" }} + kubectl port-forward service/{{ include "devops-info-service.fullname" . }} 8080:{{ .Values.service.port }} + curl http://127.0.0.1:8080 +{{- else if eq .Values.service.type "LoadBalancer" }} + kubectl get svc {{ include "devops-info-service.fullname" . }} +{{- else }} + kubectl port-forward service/{{ include "devops-info-service.fullname" . }} 8080:{{ .Values.service.port }} +{{- end }} diff --git a/labs/lab10/k8s/devops-info-service/templates/_helpers.tpl b/labs/lab10/k8s/devops-info-service/templates/_helpers.tpl new file mode 100644 index 0000000000..65e0dc68bc --- /dev/null +++ b/labs/lab10/k8s/devops-info-service/templates/_helpers.tpl @@ -0,0 +1,43 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "devops-info-service.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "devops-info-service.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} + +{{/* +Chart name and version +*/}} +{{- define "devops-info-service.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "devops-info-service.labels" -}} +helm.sh/chart: {{ include "devops-info-service.chart" . }} +{{ include "devops-info-service.selectorLabels" . }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "devops-info-service.selectorLabels" -}} +app.kubernetes.io/name: {{ include "devops-info-service.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/labs/lab10/k8s/devops-info-service/templates/deployment.yaml b/labs/lab10/k8s/devops-info-service/templates/deployment.yaml new file mode 100644 index 0000000000..8c28093ddc --- /dev/null +++ b/labs/lab10/k8s/devops-info-service/templates/deployment.yaml @@ -0,0 +1,54 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "devops-info-service.fullname" . }} + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + selector: + matchLabels: + {{- include "devops-info-service.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "devops-info-service.selectorLabels" . | nindent 8 }} + spec: + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + ports: + - containerPort: {{ .Values.service.targetPort }} + protocol: TCP + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: {{ .Values.livenessProbe.httpGet.path }} + port: {{ .Values.livenessProbe.httpGet.port }} + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: {{ .Values.readinessProbe.httpGet.path }} + port: {{ .Values.readinessProbe.httpGet.port }} + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- end }} diff --git a/labs/lab10/k8s/devops-info-service/templates/hooks/post-install-job.yaml b/labs/lab10/k8s/devops-info-service/templates/hooks/post-install-job.yaml new file mode 100644 index 0000000000..85b4f09777 --- /dev/null +++ b/labs/lab10/k8s/devops-info-service/templates/hooks/post-install-job.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: "{{ include "devops-info-service.fullname" . }}-post-install" + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": post-install + "helm.sh/hook-weight": "5" + "helm.sh/hook-delete-policy": hook-succeeded +spec: + template: + metadata: + name: "{{ include "devops-info-service.fullname" . }}-post-install" + spec: + restartPolicy: Never + containers: + - name: post-install-job + image: {{ .Values.hookJobs.image }} + imagePullPolicy: {{ .Values.hookJobs.pullPolicy }} + command: + - sh + - -c + - | + echo "Post-install smoke test started" + sleep 5 + echo "Post-install smoke test completed" diff --git a/labs/lab10/k8s/devops-info-service/templates/hooks/pre-install-job.yaml b/labs/lab10/k8s/devops-info-service/templates/hooks/pre-install-job.yaml new file mode 100644 index 0000000000..76695e8ed9 --- /dev/null +++ b/labs/lab10/k8s/devops-info-service/templates/hooks/pre-install-job.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: "{{ include "devops-info-service.fullname" . }}-pre-install" + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": pre-install + "helm.sh/hook-weight": "-5" + "helm.sh/hook-delete-policy": hook-succeeded +spec: + template: + metadata: + name: "{{ include "devops-info-service.fullname" . }}-pre-install" + spec: + restartPolicy: Never + containers: + - name: pre-install-job + image: {{ .Values.hookJobs.image }} + imagePullPolicy: {{ .Values.hookJobs.pullPolicy }} + command: + - sh + - -c + - | + echo "Pre-install validation started" + sleep 5 + echo "Pre-install validation completed" diff --git a/labs/lab10/k8s/devops-info-service/templates/service.yaml b/labs/lab10/k8s/devops-info-service/templates/service.yaml new file mode 100644 index 0000000000..ddb8e07756 --- /dev/null +++ b/labs/lab10/k8s/devops-info-service/templates/service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "devops-info-service.fullname" . }} + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + selector: + {{- include "devops-info-service.selectorLabels" . | nindent 4 }} + ports: + - protocol: TCP + port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} + {{- if eq .Values.service.type "NodePort" }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} diff --git a/labs/lab10/k8s/devops-info-service/values-dev.yaml b/labs/lab10/k8s/devops-info-service/values-dev.yaml new file mode 100644 index 0000000000..27a7731343 --- /dev/null +++ b/labs/lab10/k8s/devops-info-service/values-dev.yaml @@ -0,0 +1,26 @@ +replicaCount: 1 + +image: + tag: "latest" + +service: + type: NodePort + port: 80 + targetPort: 5000 + nodePort: 30081 + +resources: + requests: + cpu: "50m" + memory: "64Mi" + limits: + cpu: "100m" + memory: "128Mi" + +livenessProbe: + initialDelaySeconds: 5 + periodSeconds: 10 + +readinessProbe: + initialDelaySeconds: 3 + periodSeconds: 5 diff --git a/labs/lab10/k8s/devops-info-service/values-prod.yaml b/labs/lab10/k8s/devops-info-service/values-prod.yaml new file mode 100644 index 0000000000..634150c7f3 --- /dev/null +++ b/labs/lab10/k8s/devops-info-service/values-prod.yaml @@ -0,0 +1,25 @@ +replicaCount: 3 + +image: + tag: "latest" + +service: + type: LoadBalancer + port: 80 + targetPort: 5000 + +resources: + requests: + cpu: "200m" + memory: "256Mi" + limits: + cpu: "500m" + memory: "512Mi" + +livenessProbe: + initialDelaySeconds: 20 + periodSeconds: 5 + +readinessProbe: + initialDelaySeconds: 10 + periodSeconds: 5 diff --git a/labs/lab10/k8s/devops-info-service/values.yaml b/labs/lab10/k8s/devops-info-service/values.yaml new file mode 100644 index 0000000000..544a71398a --- /dev/null +++ b/labs/lab10/k8s/devops-info-service/values.yaml @@ -0,0 +1,50 @@ +replicaCount: 3 + +nameOverride: "" +fullnameOverride: "" + +image: + repository: fayzullin/devops-info-service + tag: "latest" + pullPolicy: Always + +service: + type: NodePort + port: 80 + targetPort: 5000 + nodePort: 30080 + +resources: + requests: + cpu: "100m" + memory: "128Mi" + limits: + cpu: "200m" + memory: "256Mi" + +livenessProbe: + enabled: true + httpGet: + path: /health + port: 5000 + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 3 + +readinessProbe: + enabled: true + httpGet: + path: /health + port: 5000 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + failureThreshold: 3 + +hookJobs: + image: busybox + pullPolicy: IfNotPresent + +podSecurityContext: {} + diff --git a/labs/lab11/k8s/SECRETS.md b/labs/lab11/k8s/SECRETS.md new file mode 100644 index 0000000000..60306f19ee --- /dev/null +++ b/labs/lab11/k8s/SECRETS.md @@ -0,0 +1,327 @@ +# Lab 11 — Kubernetes Secrets & HashiCorp Vault + +## 1. Kubernetes Secrets + +### Secret creation with kubectl + +A Kubernetes Secret named `app-credentials` was created using the imperative command: + +```bash +kubectl create secret generic app-credentials \ + --from-literal=username=devuser \ + --from-literal=password=devpass123 +``` + +### Viewing the secret + +```bash +kubectl get secret app-credentials -o yaml +``` + +Example output: + +apiVersion: v1 +data: + password: ZGV2cGFzczEyMw== + username: ZGV2dXNlcg== +kind: Secret +metadata: + name: app-credentials +type: Opaque + +### Decoding the values + +```bash +echo "ZGV2dXNlcg==" | base64 -d +echo +echo "ZGV2cGFzczEyMw==" | base64 -d +echo +``` + +Decoded values: + +username = devuser +password = devpass123 + +### Base64 encoding vs encryption + +Kubernetes Secrets store values in base64-encoded format. +Base64 is only encoding, not encryption. + +This means: + +anyone with access to the Secret object can decode the values +Secrets are not automatically strongly protected just because they are stored as Secret resources +Security implications + +For production environments: + +RBAC should restrict access to Secrets +encryption at rest should be enabled for etcd +external secret managers such as Vault are recommended for stronger security + +## 2. Helm Secret Integration + +### Chart structure + +The Helm chart was extended with secret management: + +labs/lab11/k8s/devops-info-service/ +├── Chart.yaml +├── values.yaml +├── values-dev.yaml +├── values-prod.yaml +└── templates/ + ├── deployment.yaml + ├── service.yaml + ├── secrets.yaml + ├── serviceaccount.yaml + ├── _helpers.tpl + ├── NOTES.txt + └── hooks/ + +### Secret template + +A new template file was added: + +templates/secrets.yaml + +This template creates a Kubernetes Secret using values from Helm configuration. + +Secret values + +Secret values are defined in: + +values.yaml with placeholder defaults +values-dev.yaml with development values +values-prod.yaml with placeholder production values +Secret consumption in Deployment + +The Deployment consumes the Secret using: + +envFrom: + - secretRef: + name: + + +### Verification inside the pod + +The Helm release was installed: + +```bash +helm install secrets-release . -f values-dev.yaml +``` + +The created Secret: + +```bash +kubectl get secrets +``` + +Example output included: + +app-credentials +secrets-release-devops-info-service-secret + +Environment variables inside the pod were verified with: + +```bash +kubectl exec -it secrets-release-devops-info-service-7b8848dbcd-7vrb2 -- env | grep -i -E 'username|password' +``` + +Output: + +password=devpass123 +username=devuser + +### Pod description verification + +```bash +kubectl describe pod secrets-release-devops-info-service-7b8848dbcd-7vrb2 +``` + +The pod description showed: + +Environment Variables from: + secrets-release-devops-info-service-secret Secret Optional: false + +The actual secret values were not shown in kubectl describe pod, only the reference to the Secret. + +## 3. Resource Management + +### Configured resources + +The Deployment includes configurable CPU and memory requests/limits. + +Example configuration: + +resources: + requests: + cpu: "50m" + memory: "64Mi" + limits: + cpu: "100m" + memory: "128Mi" + +### Requests vs limits + +Requests define the minimum amount of CPU and memory required for scheduling +Limits define the maximum amount of CPU and memory the container is allowed to use + +### Choosing values + +For this lab: + +lower values were used in development for local cluster efficiency +the chart still follows Kubernetes resource management best practices +values remain configurable through Helm + +## 4. Vault Integration + +### Vault installation + +Vault was installed using Helm: + +```bash +helm repo add hashicorp https://helm.releases.hashicorp.com +helm repo update + +helm install vault hashicorp/vault \ + --set "server.dev.enabled=true" \ + --set "injector.enabled=true" +``` + +### Vault installation verification + +```bash +kubectl get pods +``` + +Relevant running resources: + +vault-0 +vault-agent-injector-... + +### Secret creation in Vault + +Inside the Vault pod, a secret was written to the KV path: + +```bash +vault kv put secret/devops-info-service/config username="vaultuser" password="vaultpass123" +``` + +### Kubernetes auth configuration + +Vault Kubernetes authentication was configured with: + +Kubernetes auth method +policy devops-info-service +role devops-info-service + +The role was bound to the application service account: + +vault-release-devops-info-service +Vault Agent injection + +Vault annotations were enabled in the Deployment template. + +Verified annotations from the pod: + +vault.hashicorp.com/agent-inject: true +vault.hashicorp.com/agent-inject-secret-config: secret/data/devops-info-service/config +vault.hashicorp.com/agent-inject-status: injected +vault.hashicorp.com/role: devops-info-service + + +### Sidecar injection pattern + +The injected pod contained: + +init container: vault-agent-init +application container: devops-info-service +sidecar container: vault-agent + +This demonstrates the Vault Agent sidecar injection pattern: + +the init container prepares authentication and secret rendering +the sidecar agent keeps Vault integration active +the application reads secrets from files mounted into the pod + +### Proof of secret injection + +Secrets were verified inside the pod: + +```bash +kubectl exec -it vault-release-devops-info-service-64db8d7688-xlnhp -c devops-info-service -- ls -R /vault/secrets +kubectl exec -it vault-release-devops-info-service-64db8d7688-xlnhp -c devops-info-service -- cat /vault/secrets +``` + +config + +Output: + +/vault/secrets: +config + +Rendered content: + +data: map[password:vaultpass123 username:vaultuser] +metadata: map[created_time:2026-04-09T08:46:08.785957176Z custom_metadata: deletion_time: destroyed:false version:1] + +This confirms that Vault successfully injected the application secret into the pod filesystem. + +## 5. Security Analysis + +### Kubernetes Secrets + +Advantages + +built into Kubernetes +easy to create and use +simple integration with pods via env vars or mounted volumes + +Disadvantages + +values are only base64-encoded +not strongly protected without etcd encryption at rest +secret lifecycle management is limited +not ideal for larger production environments +HashiCorp Vault + +Advantages + +centralized secret management +policy-based access control +Kubernetes authentication support +sidecar injection pattern for secret delivery +better production-oriented security model + +Disadvantages + +more complex to install and configure +additional operational overhead +requires extra components and maintenance +When to use each approach + +Use Kubernetes Secrets when: + +the application is simple +the environment is small +native Kubernetes integration is sufficient + +Use Vault when: + +stronger security controls are needed +multiple applications need centralized secret management +policy-based access control is required +production-grade secret handling is needed +Production recommendations + +For production environments: + +never commit real secrets to Git +use placeholder values in Helm files +enable etcd encryption at rest +restrict access to Secrets with RBAC +prefer Vault or another external secret manager for sensitive workloads +avoid using the default service account for Vault-authenticated workloads diff --git a/labs/lab11/k8s/devops-info-service/.helmignore b/labs/lab11/k8s/devops-info-service/.helmignore new file mode 100644 index 0000000000..0e8a0eb36f --- /dev/null +++ b/labs/lab11/k8s/devops-info-service/.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/labs/lab11/k8s/devops-info-service/Chart.yaml b/labs/lab11/k8s/devops-info-service/Chart.yaml new file mode 100644 index 0000000000..ad4efa474e --- /dev/null +++ b/labs/lab11/k8s/devops-info-service/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: devops-info-service +description: Helm chart for the DevOps course FastAPI info service +type: application +version: 0.1.0 +appVersion: "1.0.0" + +keywords: + - fastapi + - python + - kubernetes + - helm + +maintainers: + - name: Fayzullin + +sources: + - https://github.com/inno-devops-labs/DevOps-Core-Course diff --git a/labs/lab11/k8s/devops-info-service/templates/NOTES.txt b/labs/lab11/k8s/devops-info-service/templates/NOTES.txt new file mode 100644 index 0000000000..cfd6d4d3bb --- /dev/null +++ b/labs/lab11/k8s/devops-info-service/templates/NOTES.txt @@ -0,0 +1,10 @@ +1. Get the application URL by running these commands: + +{{- if eq .Values.service.type "NodePort" }} + kubectl port-forward service/{{ include "devops-info-service.fullname" . }} 8080:{{ .Values.service.port }} + curl http://127.0.0.1:8080 +{{- else if eq .Values.service.type "LoadBalancer" }} + kubectl get svc {{ include "devops-info-service.fullname" . }} +{{- else }} + kubectl port-forward service/{{ include "devops-info-service.fullname" . }} 8080:{{ .Values.service.port }} +{{- end }} diff --git a/labs/lab11/k8s/devops-info-service/templates/_helpers.tpl b/labs/lab11/k8s/devops-info-service/templates/_helpers.tpl new file mode 100644 index 0000000000..4fbef766cc --- /dev/null +++ b/labs/lab11/k8s/devops-info-service/templates/_helpers.tpl @@ -0,0 +1,54 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "devops-info-service.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "devops-info-service.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} + +{{/* +Chart name and version +*/}} +{{- define "devops-info-service.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "devops-info-service.labels" -}} +helm.sh/chart: {{ include "devops-info-service.chart" . }} +{{ include "devops-info-service.selectorLabels" . }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "devops-info-service.selectorLabels" -}} +app.kubernetes.io/name: {{ include "devops-info-service.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Service account name +*/}} +{{- define "devops-info-service.serviceAccountName" -}} +{{- if .Values.serviceAccount.name }} +{{- .Values.serviceAccount.name }} +{{- else }} +{{- include "devops-info-service.fullname" . }} +{{- end }} +{{- end }} diff --git a/labs/lab11/k8s/devops-info-service/templates/deployment.yaml b/labs/lab11/k8s/devops-info-service/templates/deployment.yaml new file mode 100644 index 0000000000..c16125d672 --- /dev/null +++ b/labs/lab11/k8s/devops-info-service/templates/deployment.yaml @@ -0,0 +1,66 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "devops-info-service.fullname" . }} + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + selector: + matchLabels: + {{- include "devops-info-service.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "devops-info-service.selectorLabels" . | nindent 8 }} + {{- if .Values.vault.enabled }} + annotations: + vault.hashicorp.com/agent-inject: "true" + vault.hashicorp.com/role: {{ .Values.vault.role | quote }} + vault.hashicorp.com/agent-inject-secret-{{ .Values.vault.injectFileName }}: {{ .Values.vault.secretPath | quote }} + {{- end }} + spec: + serviceAccountName: {{ include "devops-info-service.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + ports: + - containerPort: {{ .Values.service.targetPort }} + protocol: TCP + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- if .Values.secret.enabled }} + envFrom: + - secretRef: + name: {{ include "devops-info-service.fullname" . }}-{{ .Values.secret.nameSuffix }} + {{- end }} + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: {{ .Values.livenessProbe.httpGet.path }} + port: {{ .Values.livenessProbe.httpGet.port }} + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: {{ .Values.readinessProbe.httpGet.path }} + port: {{ .Values.readinessProbe.httpGet.port }} + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- end }} diff --git a/labs/lab11/k8s/devops-info-service/templates/hooks/post-install-job.yaml b/labs/lab11/k8s/devops-info-service/templates/hooks/post-install-job.yaml new file mode 100644 index 0000000000..85b4f09777 --- /dev/null +++ b/labs/lab11/k8s/devops-info-service/templates/hooks/post-install-job.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: "{{ include "devops-info-service.fullname" . }}-post-install" + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": post-install + "helm.sh/hook-weight": "5" + "helm.sh/hook-delete-policy": hook-succeeded +spec: + template: + metadata: + name: "{{ include "devops-info-service.fullname" . }}-post-install" + spec: + restartPolicy: Never + containers: + - name: post-install-job + image: {{ .Values.hookJobs.image }} + imagePullPolicy: {{ .Values.hookJobs.pullPolicy }} + command: + - sh + - -c + - | + echo "Post-install smoke test started" + sleep 5 + echo "Post-install smoke test completed" diff --git a/labs/lab11/k8s/devops-info-service/templates/hooks/pre-install-job.yaml b/labs/lab11/k8s/devops-info-service/templates/hooks/pre-install-job.yaml new file mode 100644 index 0000000000..76695e8ed9 --- /dev/null +++ b/labs/lab11/k8s/devops-info-service/templates/hooks/pre-install-job.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: "{{ include "devops-info-service.fullname" . }}-pre-install" + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": pre-install + "helm.sh/hook-weight": "-5" + "helm.sh/hook-delete-policy": hook-succeeded +spec: + template: + metadata: + name: "{{ include "devops-info-service.fullname" . }}-pre-install" + spec: + restartPolicy: Never + containers: + - name: pre-install-job + image: {{ .Values.hookJobs.image }} + imagePullPolicy: {{ .Values.hookJobs.pullPolicy }} + command: + - sh + - -c + - | + echo "Pre-install validation started" + sleep 5 + echo "Pre-install validation completed" diff --git a/labs/lab11/k8s/devops-info-service/templates/secrets.yaml b/labs/lab11/k8s/devops-info-service/templates/secrets.yaml new file mode 100644 index 0000000000..8bf4e1d833 --- /dev/null +++ b/labs/lab11/k8s/devops-info-service/templates/secrets.yaml @@ -0,0 +1,12 @@ +{{- if .Values.secret.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "devops-info-service.fullname" . }}-{{ .Values.secret.nameSuffix }} + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} +type: Opaque +stringData: + username: {{ .Values.secret.username | quote }} + password: {{ .Values.secret.password | quote }} +{{- end }} diff --git a/labs/lab11/k8s/devops-info-service/templates/service.yaml b/labs/lab11/k8s/devops-info-service/templates/service.yaml new file mode 100644 index 0000000000..ddb8e07756 --- /dev/null +++ b/labs/lab11/k8s/devops-info-service/templates/service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "devops-info-service.fullname" . }} + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + selector: + {{- include "devops-info-service.selectorLabels" . | nindent 4 }} + ports: + - protocol: TCP + port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} + {{- if eq .Values.service.type "NodePort" }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} diff --git a/labs/lab11/k8s/devops-info-service/templates/serviceaccount.yaml b/labs/lab11/k8s/devops-info-service/templates/serviceaccount.yaml new file mode 100644 index 0000000000..10279cd57d --- /dev/null +++ b/labs/lab11/k8s/devops-info-service/templates/serviceaccount.yaml @@ -0,0 +1,8 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "devops-info-service.serviceAccountName" . }} + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} +{{- end }} diff --git a/labs/lab11/k8s/devops-info-service/values-dev.yaml b/labs/lab11/k8s/devops-info-service/values-dev.yaml new file mode 100644 index 0000000000..e58a79851a --- /dev/null +++ b/labs/lab11/k8s/devops-info-service/values-dev.yaml @@ -0,0 +1,36 @@ +replicaCount: 1 + +image: + tag: "latest" + +service: + type: NodePort + port: 80 + targetPort: 5000 + nodePort: 30081 + +resources: + requests: + cpu: "50m" + memory: "64Mi" + limits: + cpu: "100m" + memory: "128Mi" + +livenessProbe: + initialDelaySeconds: 5 + periodSeconds: 10 + +readinessProbe: + initialDelaySeconds: 3 + periodSeconds: 5 + +secret: + username: "devuser" + password: "devpass123" + +vault: + enabled: true + role: "devops-info-service" + secretPath: "secret/data/devops-info-service/config" + injectFileName: "config" diff --git a/labs/lab11/k8s/devops-info-service/values-prod.yaml b/labs/lab11/k8s/devops-info-service/values-prod.yaml new file mode 100644 index 0000000000..d0893ff843 --- /dev/null +++ b/labs/lab11/k8s/devops-info-service/values-prod.yaml @@ -0,0 +1,32 @@ +replicaCount: 3 + +image: + tag: "latest" + +service: + type: LoadBalancer + port: 80 + targetPort: 5000 + +resources: + requests: + cpu: "200m" + memory: "256Mi" + limits: + cpu: "500m" + memory: "512Mi" + +livenessProbe: + initialDelaySeconds: 20 + periodSeconds: 5 + +readinessProbe: + initialDelaySeconds: 10 + periodSeconds: 5 + +secret: + username: "prod-placeholder-user" + password: "prod-placeholder-password" + +vault: + enabled: false diff --git a/labs/lab11/k8s/devops-info-service/values.yaml b/labs/lab11/k8s/devops-info-service/values.yaml new file mode 100644 index 0000000000..b3defdeb4d --- /dev/null +++ b/labs/lab11/k8s/devops-info-service/values.yaml @@ -0,0 +1,65 @@ +replicaCount: 3 + +nameOverride: "" +fullnameOverride: "" + +image: + repository: fayzullin/devops-info-service + tag: "latest" + pullPolicy: Always + +service: + type: NodePort + port: 80 + targetPort: 5000 + nodePort: 30080 + +resources: + requests: + cpu: "100m" + memory: "128Mi" + limits: + cpu: "200m" + memory: "256Mi" + +livenessProbe: + enabled: true + httpGet: + path: /health + port: 5000 + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 3 + +readinessProbe: + enabled: true + httpGet: + path: /health + port: 5000 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + failureThreshold: 3 + +hookJobs: + image: busybox + pullPolicy: IfNotPresent + +podSecurityContext: {} + +secret: + enabled: true + nameSuffix: secret + username: "placeholder-user" + password: "placeholder-password" + +serviceAccount: + create: true + name: "" + +vault: + enabled: false + role: "devops-info-service" + secretPath: "secret/data/devops-info-service/config" + injectFileName: "config" diff --git a/labs/lab12/k8s/CONFIGMAPS.md b/labs/lab12/k8s/CONFIGMAPS.md new file mode 100644 index 0000000000..85f9e41fee --- /dev/null +++ b/labs/lab12/k8s/CONFIGMAPS.md @@ -0,0 +1,126 @@ +# Lab 12 — ConfigMaps & Persistent Volumes + +## 1. Application Changes + +The application was updated to support a persistent visits counter. + +### Changes: +- A visits counter is stored in `/data/visits` +- Each request to `/` increments the counter +- A new endpoint `/visits` returns the current counter +- The file is read on startup (default = 0 if not exists) + +### Endpoints: +- `GET /` — increments visits counter +- `GET /visits` — returns current visits count + +### Local Docker Test + +```bash +docker run --rm -p 5000:5000 -v "$(pwd)/data:/data" fayzullin/devops-info-service:latest +``` + +### Verification: + +Counter increased after requests +Value persisted after container restart + +## 2. ConfigMap Implementation +File-based ConfigMap + +Config file: + +{ + "appName": "devops-info-service", + "environment": "dev", + "featureFlag": true +} + +Mounted inside pod: + +```bash +kubectl exec -it -- cat /config/config.json +``` + +Output: + +{ + "appName": "devops-info-service", + "environment": "dev", + "featureFlag": true +} +Environment Variables via ConfigMap +kubectl exec -it -- printenv | grep APP_ +kubectl exec -it -- printenv | grep LOG_LEVEL + +Output: + +APP_ENV=dev +LOG_LEVEL=debug + +## 3. Persistent Volume + +### PVC + +```bash +kubectl get pvc +``` + +Output: + +lab12-release-devops-info-service-data Bound 100Mi RWO +Volume Mount + +PVC is mounted at: + +/data +Persistence Verification + +Before pod restart: + +cat /data/visits + +Output: + +2 + +After pod recreation: + +kubectl delete pod +kubectl get pods +kubectl exec -it -- cat /data/visits + +Output: + +2 + +After new request: + +curl localhost:8080/visits + +Output: + +{"visits":3} + +✅ Data persisted across pod restart + +## 4. ConfigMap vs Secret +ConfigMap Secret +Non-sensitive data Sensitive data +App config Passwords, tokens +Plain text Base64 encoded +Example: APP_ENV Example: DB_PASSWORD + +## 5. Summary + +In this lab: + +ConfigMaps were used for configuration (file + env vars) +PersistentVolumeClaim was used for data storage +Application data persisted across pod restarts +/visits endpoint confirmed correct behavior + +The application is now production-ready with: + +externalized configuration +persistent storage diff --git a/labs/lab12/k8s/devops-info-service/.helmignore b/labs/lab12/k8s/devops-info-service/.helmignore new file mode 100644 index 0000000000..0e8a0eb36f --- /dev/null +++ b/labs/lab12/k8s/devops-info-service/.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/labs/lab12/k8s/devops-info-service/Chart.yaml b/labs/lab12/k8s/devops-info-service/Chart.yaml new file mode 100644 index 0000000000..ad4efa474e --- /dev/null +++ b/labs/lab12/k8s/devops-info-service/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: devops-info-service +description: Helm chart for the DevOps course FastAPI info service +type: application +version: 0.1.0 +appVersion: "1.0.0" + +keywords: + - fastapi + - python + - kubernetes + - helm + +maintainers: + - name: Fayzullin + +sources: + - https://github.com/inno-devops-labs/DevOps-Core-Course diff --git a/labs/lab12/k8s/devops-info-service/files/config.json b/labs/lab12/k8s/devops-info-service/files/config.json new file mode 100644 index 0000000000..6c5b067a26 --- /dev/null +++ b/labs/lab12/k8s/devops-info-service/files/config.json @@ -0,0 +1,5 @@ +{ + "appName": "devops-info-service", + "environment": "dev", + "featureFlag": true +} diff --git a/labs/lab12/k8s/devops-info-service/templates/NOTES.txt b/labs/lab12/k8s/devops-info-service/templates/NOTES.txt new file mode 100644 index 0000000000..cfd6d4d3bb --- /dev/null +++ b/labs/lab12/k8s/devops-info-service/templates/NOTES.txt @@ -0,0 +1,10 @@ +1. Get the application URL by running these commands: + +{{- if eq .Values.service.type "NodePort" }} + kubectl port-forward service/{{ include "devops-info-service.fullname" . }} 8080:{{ .Values.service.port }} + curl http://127.0.0.1:8080 +{{- else if eq .Values.service.type "LoadBalancer" }} + kubectl get svc {{ include "devops-info-service.fullname" . }} +{{- else }} + kubectl port-forward service/{{ include "devops-info-service.fullname" . }} 8080:{{ .Values.service.port }} +{{- end }} diff --git a/labs/lab12/k8s/devops-info-service/templates/_helpers.tpl b/labs/lab12/k8s/devops-info-service/templates/_helpers.tpl new file mode 100644 index 0000000000..4fbef766cc --- /dev/null +++ b/labs/lab12/k8s/devops-info-service/templates/_helpers.tpl @@ -0,0 +1,54 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "devops-info-service.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "devops-info-service.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} + +{{/* +Chart name and version +*/}} +{{- define "devops-info-service.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "devops-info-service.labels" -}} +helm.sh/chart: {{ include "devops-info-service.chart" . }} +{{ include "devops-info-service.selectorLabels" . }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "devops-info-service.selectorLabels" -}} +app.kubernetes.io/name: {{ include "devops-info-service.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Service account name +*/}} +{{- define "devops-info-service.serviceAccountName" -}} +{{- if .Values.serviceAccount.name }} +{{- .Values.serviceAccount.name }} +{{- else }} +{{- include "devops-info-service.fullname" . }} +{{- end }} +{{- end }} diff --git a/labs/lab12/k8s/devops-info-service/templates/configmap-env.yaml b/labs/lab12/k8s/devops-info-service/templates/configmap-env.yaml new file mode 100644 index 0000000000..6d2e9a8e42 --- /dev/null +++ b/labs/lab12/k8s/devops-info-service/templates/configmap-env.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "devops-info-service.fullname" . }}-env + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} +data: + APP_ENV: {{ .Values.environment | quote }} + LOG_LEVEL: {{ .Values.logLevel | quote }} diff --git a/labs/lab12/k8s/devops-info-service/templates/configmap.yaml b/labs/lab12/k8s/devops-info-service/templates/configmap.yaml new file mode 100644 index 0000000000..03dbfb3109 --- /dev/null +++ b/labs/lab12/k8s/devops-info-service/templates/configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "devops-info-service.fullname" . }}-config + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} +data: + config.json: |- +{{ .Files.Get "files/config.json" | indent 4 }} diff --git a/labs/lab12/k8s/devops-info-service/templates/deployment.yaml b/labs/lab12/k8s/devops-info-service/templates/deployment.yaml new file mode 100644 index 0000000000..a8b777aa1e --- /dev/null +++ b/labs/lab12/k8s/devops-info-service/templates/deployment.yaml @@ -0,0 +1,80 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "devops-info-service.fullname" . }} + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + selector: + matchLabels: + {{- include "devops-info-service.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "devops-info-service.selectorLabels" . | nindent 8 }} + {{- if .Values.vault.enabled }} + annotations: + vault.hashicorp.com/agent-inject: "true" + vault.hashicorp.com/role: {{ .Values.vault.role | quote }} + vault.hashicorp.com/agent-inject-secret-{{ .Values.vault.injectFileName }}: {{ .Values.vault.secretPath | quote }} + {{- end }} + spec: + serviceAccountName: {{ include "devops-info-service.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + volumes: + - name: config-volume + configMap: + name: {{ include "devops-info-service.fullname" . }}-config + - name: data-volume + persistentVolumeClaim: + claimName: {{ include "devops-info-service.fullname" . }}-data + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + ports: + - containerPort: {{ .Values.service.targetPort }} + protocol: TCP + resources: + {{- toYaml .Values.resources | nindent 12 }} + envFrom: + - configMapRef: + name: {{ include "devops-info-service.fullname" . }}-env + {{- if .Values.secret.enabled }} + - secretRef: + name: {{ include "devops-info-service.fullname" . }}-{{ .Values.secret.nameSuffix }} + {{- end }} + volumeMounts: + - name: config-volume + mountPath: /config + - name: data-volume + mountPath: /data + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: {{ .Values.livenessProbe.httpGet.path }} + port: {{ .Values.livenessProbe.httpGet.port }} + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: {{ .Values.readinessProbe.httpGet.path }} + port: {{ .Values.readinessProbe.httpGet.port }} + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- end }} diff --git a/labs/lab12/k8s/devops-info-service/templates/hooks/post-install-job.yaml b/labs/lab12/k8s/devops-info-service/templates/hooks/post-install-job.yaml new file mode 100644 index 0000000000..85b4f09777 --- /dev/null +++ b/labs/lab12/k8s/devops-info-service/templates/hooks/post-install-job.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: "{{ include "devops-info-service.fullname" . }}-post-install" + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": post-install + "helm.sh/hook-weight": "5" + "helm.sh/hook-delete-policy": hook-succeeded +spec: + template: + metadata: + name: "{{ include "devops-info-service.fullname" . }}-post-install" + spec: + restartPolicy: Never + containers: + - name: post-install-job + image: {{ .Values.hookJobs.image }} + imagePullPolicy: {{ .Values.hookJobs.pullPolicy }} + command: + - sh + - -c + - | + echo "Post-install smoke test started" + sleep 5 + echo "Post-install smoke test completed" diff --git a/labs/lab12/k8s/devops-info-service/templates/hooks/pre-install-job.yaml b/labs/lab12/k8s/devops-info-service/templates/hooks/pre-install-job.yaml new file mode 100644 index 0000000000..76695e8ed9 --- /dev/null +++ b/labs/lab12/k8s/devops-info-service/templates/hooks/pre-install-job.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: "{{ include "devops-info-service.fullname" . }}-pre-install" + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": pre-install + "helm.sh/hook-weight": "-5" + "helm.sh/hook-delete-policy": hook-succeeded +spec: + template: + metadata: + name: "{{ include "devops-info-service.fullname" . }}-pre-install" + spec: + restartPolicy: Never + containers: + - name: pre-install-job + image: {{ .Values.hookJobs.image }} + imagePullPolicy: {{ .Values.hookJobs.pullPolicy }} + command: + - sh + - -c + - | + echo "Pre-install validation started" + sleep 5 + echo "Pre-install validation completed" diff --git a/labs/lab12/k8s/devops-info-service/templates/pvc.yaml b/labs/lab12/k8s/devops-info-service/templates/pvc.yaml new file mode 100644 index 0000000000..862717f000 --- /dev/null +++ b/labs/lab12/k8s/devops-info-service/templates/pvc.yaml @@ -0,0 +1,17 @@ +{{- if .Values.persistence.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "devops-info-service.fullname" . }}-data + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.persistence.size }} + {{- if .Values.persistence.storageClass }} + storageClassName: {{ .Values.persistence.storageClass | quote }} + {{- end }} +{{- end }} diff --git a/labs/lab12/k8s/devops-info-service/templates/secrets.yaml b/labs/lab12/k8s/devops-info-service/templates/secrets.yaml new file mode 100644 index 0000000000..8bf4e1d833 --- /dev/null +++ b/labs/lab12/k8s/devops-info-service/templates/secrets.yaml @@ -0,0 +1,12 @@ +{{- if .Values.secret.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "devops-info-service.fullname" . }}-{{ .Values.secret.nameSuffix }} + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} +type: Opaque +stringData: + username: {{ .Values.secret.username | quote }} + password: {{ .Values.secret.password | quote }} +{{- end }} diff --git a/labs/lab12/k8s/devops-info-service/templates/service.yaml b/labs/lab12/k8s/devops-info-service/templates/service.yaml new file mode 100644 index 0000000000..ddb8e07756 --- /dev/null +++ b/labs/lab12/k8s/devops-info-service/templates/service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "devops-info-service.fullname" . }} + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + selector: + {{- include "devops-info-service.selectorLabels" . | nindent 4 }} + ports: + - protocol: TCP + port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} + {{- if eq .Values.service.type "NodePort" }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} diff --git a/labs/lab12/k8s/devops-info-service/templates/serviceaccount.yaml b/labs/lab12/k8s/devops-info-service/templates/serviceaccount.yaml new file mode 100644 index 0000000000..10279cd57d --- /dev/null +++ b/labs/lab12/k8s/devops-info-service/templates/serviceaccount.yaml @@ -0,0 +1,8 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "devops-info-service.serviceAccountName" . }} + labels: + {{- include "devops-info-service.labels" . | nindent 4 }} +{{- end }} diff --git a/labs/lab12/k8s/devops-info-service/values-dev.yaml b/labs/lab12/k8s/devops-info-service/values-dev.yaml new file mode 100644 index 0000000000..7a69be080f --- /dev/null +++ b/labs/lab12/k8s/devops-info-service/values-dev.yaml @@ -0,0 +1,39 @@ +replicaCount: 1 + +image: + tag: "latest" + +service: + type: NodePort + port: 80 + targetPort: 5000 + nodePort: 30081 + +resources: + requests: + cpu: "50m" + memory: "64Mi" + limits: + cpu: "100m" + memory: "128Mi" + +livenessProbe: + initialDelaySeconds: 5 + periodSeconds: 10 + +readinessProbe: + initialDelaySeconds: 3 + periodSeconds: 5 + +secret: + username: "devuser" + password: "devpass123" + +vault: + enabled: true + role: "devops-info-service" + secretPath: "secret/data/devops-info-service/config" + injectFileName: "config" + +environment: "dev" +logLevel: "debug" diff --git a/labs/lab12/k8s/devops-info-service/values-prod.yaml b/labs/lab12/k8s/devops-info-service/values-prod.yaml new file mode 100644 index 0000000000..9ad7c2972f --- /dev/null +++ b/labs/lab12/k8s/devops-info-service/values-prod.yaml @@ -0,0 +1,35 @@ +replicaCount: 3 + +image: + tag: "latest" + +service: + type: LoadBalancer + port: 80 + targetPort: 5000 + +resources: + requests: + cpu: "200m" + memory: "256Mi" + limits: + cpu: "500m" + memory: "512Mi" + +livenessProbe: + initialDelaySeconds: 20 + periodSeconds: 5 + +readinessProbe: + initialDelaySeconds: 10 + periodSeconds: 5 + +secret: + username: "prod-placeholder-user" + password: "prod-placeholder-password" + +vault: + enabled: false + +environment: "prod" +logLevel: "info" diff --git a/labs/lab12/k8s/devops-info-service/values.yaml b/labs/lab12/k8s/devops-info-service/values.yaml new file mode 100644 index 0000000000..c9249afc52 --- /dev/null +++ b/labs/lab12/k8s/devops-info-service/values.yaml @@ -0,0 +1,73 @@ +replicaCount: 3 + +nameOverride: "" +fullnameOverride: "" + +image: + repository: fayzullin/devops-info-service + tag: "latest" + pullPolicy: Always + +service: + type: NodePort + port: 80 + targetPort: 5000 + nodePort: 30080 + +resources: + requests: + cpu: "100m" + memory: "128Mi" + limits: + cpu: "200m" + memory: "256Mi" + +livenessProbe: + enabled: true + httpGet: + path: /health + port: 5000 + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 3 + +readinessProbe: + enabled: true + httpGet: + path: /health + port: 5000 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + failureThreshold: 3 + +hookJobs: + image: busybox + pullPolicy: IfNotPresent + +podSecurityContext: {} + +secret: + enabled: true + nameSuffix: secret + username: "placeholder-user" + password: "placeholder-password" + +serviceAccount: + create: true + name: "" + +vault: + enabled: false + role: "devops-info-service" + secretPath: "secret/data/devops-info-service/config" + injectFileName: "config" + +environment: "dev" +logLevel: "info" + +persistence: + enabled: true + size: 100Mi + storageClass: ""