DevOps End-to-End — Learning Notes for Developers
The Big Picture — Full Pipeline at a Glance
Every modern cloud deployment follows roughly the same end-to-end journey:
Developer → Git Commit
→ CI Server (Jenkins)
→ Build (Gradle/Maven)
→ Unit Tests
→ Static Analysis (SonarQube)
→ Docker Build + Push to Registry
→ CD Pipeline
→ Terraform (provision GKE infra — runs once / when infra changes)
→ Helm (deploy/upgrade application — runs every release)
→ GitOps (Flux watches Git, auto-syncs cluster)
→ Running Application on GKE
→ Monitoring (Prometheus + Grafana, Datadog, Dynatrace)
Key mental model:
- Terraform = infrastructure layer (create the Kubernetes cluster itself)
- Helm = application layer (deploy your app onto the cluster)
- Flux = automation layer (keep the cluster in sync with what’s in Git)
These three tools answer three different questions:
| Question | Tool |
|---|---|
| Where does the app run? | Terraform |
| How is the app packaged and deployed? | Helm |
| Who triggers the deployment? | Jenkins (push) or Flux (pull/GitOps) |
Step 1 — Code Changes and Version Control
- Developers write Spring Boot application code.
- Code is committed to a Git repository (GitHub, GitLab, Bitbucket, etc.).
- A pull request / merge triggers the CI pipeline automatically via webhooks.
Branching strategy tip: Most teams use develop for QA and relese for production and
Merging to develop/release triggers a build pipeline.
Step 2 — Continuous Integration (CI)
CI means: every commit is automatically built, tested, and validated.
2.1 Jenkins Pipeline
Jenkins is the most common CI server in enterprise Java shops. Pipelines are defined as code in a Jenkinsfile:
pipeline {
agent any
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh './gradlew clean build -x test'
}
}
stage('Unit Tests') {
steps {
sh './gradlew test'
}
post {
always {
junit 'build/test-results/**/*.xml'
}
}
}
stage('Static Analysis (SonarQube)') {
steps {
sh './gradlew sonarqube'
}
}
stage('Docker Build & Push') {
steps {
script {
def image = docker.build("gcr.io/my-project/myapp:${env.BUILD_NUMBER}")
docker.withRegistry('https://gcr.io', 'gcr-credentials') {
image.push()
image.push('latest')
}
}
}
}
}
}
2.2 Static Code Analysis — SonarQube
SonarQube scans code for:
- Bugs — potential runtime errors
- Vulnerabilities — security issues (e.g., SQL injection risk)
- Code Smells — maintainability issues
- Coverage — what % of code is covered by tests
Add to build.gradle:
plugins {
id "org.sonarqube" version "3.4.0.905"
}
A quality gate is a pass/fail threshold (e.g., “coverage must be ≥ 80%”). Jenkins can be configured to fail the build if the gate is not met.
2.3 Containerization — Docker
Docker packages the application + its runtime into a portable, reproducible image. The image is the artifact that gets deployed everywhere.
# Build stage
FROM gradle:7.6-jdk17 AS builder
WORKDIR /app
COPY . .
RUN gradle build -x test
# Runtime stage (smaller final image)
FROM openjdk:17-jre-slim
COPY --from=builder /app/build/libs/myapp.jar /app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
The built image is pushed to a container registry:
-
Google Container Registry (GCR):
gcr.io/my-project/myapp:1.2.3 -
Docker Hub:
myorg/myapp:1.2.3 -
Artifact Registry (modern GCP):
us-central1-docker.pkg.dev/my-project/my-repo/myapp:1.2.3
Once pushed, the image tag (e.g., :1.2.3 or :${BUILD_NUMBER}) is used by Helm to tell Kubernetes exactly which image to run.
Step 3 — Infrastructure Provisioning with Terraform
Terraform answers: “Where does the application run?”
It provisions the cloud infrastructure — before you can deploy an app to Kubernetes, you need a Kubernetes cluster. That cluster is created by Terraform.
3.1 What Terraform manages
- Google Kubernetes Engine (GKE) clusters (the Kubernetes cluster itself)
- VPCs, subnets, firewall rules
- IAM roles and service accounts
- Cloud SQL databases, Cloud Storage buckets
- Load balancers and DNS entries
Key principle: Terraform uses HCL (HashiCorp Configuration Language) to describe desired state. It figures out what needs to be created/changed/deleted.
3.2 Terraform Lifecycle Commands
terraform init # Download providers & modules (run once per workspace)
terraform plan # Preview changes — shows what WILL be created/modified/destroyed
terraform apply # Apply changes to real infrastructure
terraform destroy # Tear down all managed resources
Always run terraform plan before apply in production — it’s your safety net.
3.3 GKE Cluster Example
provider "google" {
credentials = file("<path-to-service-account>.json")
project = "<your-project-id>"
region = "us-central1"
}
resource "google_container_cluster" "primary" {
name = "my-gke-cluster"
location = "us-central1-a"
initial_node_count = 3
node_config {
machine_type = "e2-medium"
oauth_scopes = [
"https://www.googleapis.com/auth/cloud-platform",
]
}
}
3.4 Terraform vs Helm — When does each run?
| Terraform | Helm | |
|---|---|---|
| What it manages | Cloud infrastructure (cluster, network, IAM) | Application workloads running inside the cluster |
| How often | Once at setup; re-run when infra changes | Every app release / config change |
| Triggered by | Infra team PR merged to infra repo | App CI/CD pipeline |
| State stored in | Remote state (GCS bucket, Terraform Cloud) | Kubernetes secrets (in-cluster) |
Step 4 — Application Deployment with Helm
Helm answers: “How is the application packaged and deployed onto Kubernetes?”
Think of Helm as apt or brew for Kubernetes — it packages all the YAML
manifests your app needs into a single versioned chart, and lets you install,
upgrade, and roll back with simple commands.
4.1 What a Helm Chart looks like
charts/
└── my-application/
├── Chart.yaml ← metadata (name, version, description)
├── values.yaml ← default configuration values
├── requirements.yaml ← chart dependencies (e.g., Postgres chart)
└── templates/
├── deployment.yaml ← Kubernetes Deployment manifest
├── service.yaml ← Kubernetes Service manifest
├── ingress.yaml ← Ingress / load balancer config
└── config.yaml ← ConfigMaps / Secrets
4.2 values.yaml — The configuration knob
Environment-specific values (dev vs staging vs prod) are set here or overridden at deploy time:
# values.yaml
replicaCount: 2
image:
repository: gcr.io/my-project/myapp
tag: "latest" # Overridden in CI to a specific build tag
pullPolicy: IfNotPresent
service:
type: LoadBalancer
port: 80
targetPort: 8080
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
env:
DATABASE_URL: "jdbc:postgresql://my-db:5432/myapp"
4.3 Deploy / Upgrade command (used in Jenkinsfile)
stage('Deploy to Kubernetes') {
steps {
script {
sh '''
helm upgrade --install my-application ./charts/my-application \
--values ./charts/my-application/values.yaml \
--set image.tag=${BUILD_NUMBER} \
--namespace production \
--wait
'''
}
}
}
-
--installmeans: install if not already deployed, otherwise upgrade. -
--set image.tag=${BUILD_NUMBER}injects the exact Docker image tag from CI. -
--waitblocks until all pods are running (fail fast if something is wrong).
4.4 Rollback
# See release history
helm history my-application -n production
# Roll back to the previous release
helm rollback my-application -n production
# Roll back to a specific revision
helm rollback my-application 3 -n production
This is why Helm is powerful — rolling back an entire application (all K8s resources) is a single command.
Step 5 — GitOps with Flux
Flux answers: “Who keeps the cluster in sync with what’s in Git?”
5.1 GitOps Principle
Git is the single source of truth for what should be running in the cluster.
Instead of running helm upgrade manually or from Jenkins (a push model),
Flux runs inside the cluster and continuously watches the Git repo. When it
detects a change (new Helm chart version, updated values), it automatically
applies the change — a pull model.
Developer merges PR to config repo
↓
Flux detects the change (polling Git every N minutes)
↓
Flux runs `helm upgrade` internally
↓
Kubernetes cluster updated
5.2 HelmRelease CRD
Flux uses custom Kubernetes resources. A HelmRelease tells Flux which chart
to track and how to deploy it:
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: myapp
namespace: production
spec:
interval: 5m # How often Flux checks for changes
releaseName: myapp
chart:
spec:
chart: myapp
version: "1.0.0"
sourceRef:
kind: HelmRepository
name: my-charts-repo
values:
replicaCount: 3
image:
tag: "42"
5.3 Jenkins (Push) vs Flux (Pull) — When to use which
| Jenkins Push | Flux Pull (GitOps) | |
|---|---|---|
| Deployment trigger | Jenkins pipeline runs helm upgrade
|
Flux detects Git change, applies it |
| Auditability | Pipeline logs | Git commit history is the audit trail |
| Drift detection | No — cluster can diverge from Git | Yes — Flux continuously reconciles |
| Best for | Simple setups, fast feedback loops | Production-grade, compliance-heavy environments |
Step 6 — Monitoring & Observability
Once deployed, you need to know if the application is healthy.
6.1 Three Pillars of Observability & Tools
| Pillar | What it measures | Example Tools | Best for Spring Boot |
|---|---|---|---|
| Metrics | CPU, memory, request rate, latency, errors | Prometheus (time-series DB) + Grafana (visualization) (free) / CloudWatch (AWS) / Azure Monitor / Cloud Monitoring (GCP) | Spring Boot Actuator /actuator/prometheus
|
| Logs | Application and system events | ELK Stack (free) / CloudWatch Logs (AWS) / Log Analytics (Azure) / Cloud Logging (GCP) | Spring Boot Logback + SLF4J |
| Traces | Request path through microservices | Jaeger (free) / Zipkin (free) / Datadog / Dynatrace | Spring Cloud Sleuth or OpenTelemetry |
| All-in-one | Metrics + Logs + Traces together | Datadog / Dynatrace / New Relic / Splunk (paid) | Automatic instrumentation via agents |
Step 7 — Connecting the Dots
Here is the full flow summarized in order:
-
Code push: Developer pushes code to Git (feature branch → PR → merge to
main). - CI triggers: Jenkins detects the merge via webhook.
- Build & test: Gradle builds the app, runs unit tests.
- Quality gate: SonarQube analyzes code; build fails if gate not met.
- Docker image: Jenkins builds and pushes a versioned Docker image to GCR.
- Infra (one-time): Terraform has already provisioned the GKE cluster, networking, and IAM.
-
Deploy: Jenkins runs
helm upgrade --installwith the new image tag — OR — a config repo PR updates theHelmReleaseand Flux detects the change and deploys. - Reconciliation: Flux continuously ensures the cluster matches Git (drift detection).
- Monitoring: Prometheus scrapes metrics; Grafana dashboards alert on anomalies; Dynatrace provides deep APM.
Quick Reference — Common Interview Q&A
Q: What is the difference between CI and CD?
- CI (Continuous Integration): automatically build and test every commit.
- CD (Continuous Delivery/Deployment): automatically deliver tested artifacts to production (or a pre-prod environment).
Q: What is the difference between Terraform and Helm?
- Terraform provisions the infrastructure (the Kubernetes cluster, network, IAM on GCP).
- Helm deploys and manages applications inside an already-running Kubernetes cluster.
Q: What is GitOps?
- A practice where the entire desired state of the system (infrastructure + application config) is stored in Git. Automated agents (like Flux) continuously reconcile the cluster to match what is in Git.
Q: What is the difference between a Jenkins push deployment and Flux?
- Jenkins pushes a change to the cluster by running
helm upgradein a pipeline stage. - Flux pulls — it watches Git and self-applies changes. Flux also detects and corrects drift (manual changes to the cluster that diverge from Git).
Q: What does helm upgrade --install do?
- If the release does not exist yet: installs it. If it already exists: upgrades it to the new chart/values. One command covers both cases.
Q: How do you roll back a bad deployment?
-
helm rollback <release-name> -n <namespace>— reverts to the previous Helm revision in seconds.
Q: What is a quality gate in SonarQube?
- A set of conditions (e.g., code coverage ≥ 80%, no new critical bugs) that the codebase must meet. If the gate fails, CI marks the build as failed and the artifact is not promoted.