-
Notifications
You must be signed in to change notification settings - Fork 2
Creates GitOps tutorial Lesson 2: Promoting from Stage to Production #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: lesson-1
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| apiVersion: kafka.strimzi.io/v1 | ||
| kind: KafkaNodePool | ||
| metadata: | ||
| name: combined | ||
| labels: | ||
| strimzi.io/cluster: my-cluster | ||
| spec: | ||
| replicas: 1 | ||
| roles: | ||
| - controller | ||
| - broker | ||
| storage: | ||
| type: jbod | ||
| volumes: | ||
| - id: 0 | ||
| type: persistent-claim | ||
| size: 2Gi | ||
| deleteClaim: true | ||
| resources: | ||
| requests: | ||
| memory: 512Mi | ||
| cpu: 250m | ||
| limits: | ||
| memory: 1Gi | ||
| cpu: 500m |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| apiVersion: kafka.strimzi.io/v1 | ||
| kind: Kafka | ||
| metadata: | ||
| name: my-cluster | ||
| spec: | ||
| kafka: | ||
| version: 4.2.0 | ||
| metadataVersion: "4.2-IV0" | ||
| listeners: | ||
| - name: plain | ||
| port: 9092 | ||
| type: internal | ||
| tls: false | ||
| config: | ||
| offsets.topic.replication.factor: 1 | ||
| transaction.state.log.replication.factor: 1 | ||
| transaction.state.log.min.isr: 1 | ||
| default.replication.factor: 1 | ||
| min.insync.replicas: 1 | ||
| entityOperator: | ||
| topicOperator: | ||
| resources: | ||
| requests: | ||
| cpu: 100m | ||
| memory: 256Mi | ||
| limits: | ||
| cpu: 250m | ||
| memory: 384Mi |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| apiVersion: kustomize.config.k8s.io/v1beta1 | ||
| kind: Kustomization | ||
| resources: | ||
| - combined-pool.yaml | ||
| - kafka.yaml |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| apiVersion: kustomize.config.k8s.io/v1beta1 | ||
| kind: Kustomization | ||
| namespace: kafka-production | ||
| resources: | ||
| - ../../base | ||
| - namespace.yaml |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| apiVersion: v1 | ||
| kind: Namespace | ||
| metadata: | ||
| name: kafka-production | ||
| labels: | ||
| name: kafka-production |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| apiVersion: kafka.strimzi.io/v1 | ||
| kind: KafkaTopic | ||
| metadata: | ||
| name: my-first-topic | ||
| labels: | ||
| strimzi.io/cluster: my-cluster | ||
| spec: | ||
| partitions: 3 | ||
| replicas: 1 | ||
| config: | ||
| retention.ms: "86400000" | ||
| segment.bytes: "1073741824" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| apiVersion: kustomize.config.k8s.io/v1beta1 | ||
| kind: Kustomization | ||
| namespace: kafka-staging | ||
| resources: | ||
| - ../../base | ||
| - namespace.yaml | ||
| - topic.yaml |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| apiVersion: v1 | ||
| kind: Namespace | ||
| metadata: | ||
| name: kafka-staging | ||
| labels: | ||
| name: kafka-staging |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| apiVersion: kafka.strimzi.io/v1 | ||
| kind: KafkaTopic | ||
| metadata: | ||
| name: my-first-topic | ||
| labels: | ||
| strimzi.io/cluster: my-cluster | ||
| spec: | ||
| partitions: 3 | ||
| replicas: 1 | ||
| config: | ||
| retention.ms: "86400000" | ||
| segment.bytes: "1073741824" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,225 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| CLUSTER_NAME="gitops-tutorial" | ||
| GITEA_USER="tutorial-user" | ||
| GITEA_PASSWORD="tutorial-password" | ||
| GITEA_REPO="streamshub-gitops" | ||
| GITEA_HOST_PORT=3001 | ||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
|
|
||
| info() { echo -e "\033[1;34m[INFO]\033[0m $*"; } | ||
| warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; } | ||
| error() { echo -e "\033[1;31m[ERROR]\033[0m $*" >&2; } | ||
| b64decode() { echo "$1" | base64 -d 2>/dev/null || echo "$1" | base64 -D 2>/dev/null; } | ||
|
|
||
| cleanup() { | ||
| if [[ -n "${WORK_DIR:-}" ]]; then | ||
| rm -rf "${WORK_DIR}" | ||
| fi | ||
| } | ||
|
|
||
| # ─── Step 1: Validate infrastructure ────────────────────────────────────────── | ||
|
|
||
| info "Validating tutorial infrastructure..." | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These could be function in a common module |
||
|
|
||
| if ! kind get clusters 2>/dev/null | grep -q "^${CLUSTER_NAME}$"; then | ||
| error "KinD cluster '${CLUSTER_NAME}' is not running." | ||
| error "Please run the setup script first: ../00-setup/setup.sh" | ||
| exit 1 | ||
| fi | ||
|
|
||
| if ! kubectl get deployment strimzi-cluster-operator -n strimzi-operator &>/dev/null; then | ||
| error "Strimzi operator not found in namespace 'strimzi-operator'." | ||
| error "Please run the setup script first: ../00-setup/setup.sh" | ||
| exit 1 | ||
| fi | ||
|
|
||
| if ! curl -sf "http://localhost:${GITEA_HOST_PORT}/api/v1/version" >/dev/null 2>&1; then | ||
| error "Gitea is not reachable on localhost:${GITEA_HOST_PORT}." | ||
| error "Please run the setup script first: ../00-setup/setup.sh" | ||
| exit 1 | ||
| fi | ||
|
|
||
| info "Infrastructure checks passed." | ||
|
|
||
| # ─── Step 2: Clean up previous lesson state ─────────────────────────────────── | ||
|
|
||
| info "Cleaning up previous lesson state..." | ||
|
|
||
| # Remove the lesson-1 ArgoCD Application (no cascade finalizer, so this is instant). | ||
| kubectl delete application kafka-tutorial -n argocd --ignore-not-found 2>/dev/null || true | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The application name and namespaces in these commands should be variables, again probably in a common script file |
||
|
|
||
| # Also remove any lesson-2 Applications from a previous run of this script. | ||
| kubectl delete application kafka-staging kafka-production -n argocd --ignore-not-found 2>/dev/null || true | ||
|
|
||
| # Patch away Strimzi finalizers in all tutorial namespaces so deletions don't hang. | ||
| for ns in kafka-tutorial kafka-staging kafka-production; do | ||
| for cr_type in kafka kafkanodepool kafkatopic; do | ||
| kubectl get "${cr_type}" -n "${ns}" -o name 2>/dev/null | \ | ||
| xargs -r -I{} kubectl patch {} -n "${ns}" \ | ||
| --type=merge -p '{"metadata":{"finalizers":null}}' 2>/dev/null || true | ||
| done | ||
| done | ||
|
|
||
| kubectl delete namespace kafka-tutorial kafka-staging kafka-production --ignore-not-found | ||
|
|
||
| info "Previous lesson state cleaned up." | ||
|
|
||
| # ─── Step 3: Configure Strimzi to watch staging and production namespaces ────── | ||
|
|
||
| info "Configuring Strimzi operator for staging and production namespaces..." | ||
|
|
||
| for ns in kafka-staging kafka-production; do | ||
| kubectl create namespace "${ns}" 2>/dev/null || true | ||
| for rb_name in strimzi-cluster-operator strimzi-cluster-operator-entity-operator-delegation strimzi-cluster-operator-watched; do | ||
| ROLE_REF=$(kubectl get rolebinding "${rb_name}" -n strimzi-operator -o jsonpath='{.roleRef.name}' 2>/dev/null || echo "") | ||
| if [[ -n "${ROLE_REF}" ]]; then | ||
| kubectl create rolebinding "${rb_name}" \ | ||
| --namespace "${ns}" \ | ||
| --clusterrole="${ROLE_REF}" \ | ||
| --serviceaccount=strimzi-operator:strimzi-cluster-operator 2>/dev/null || true | ||
| fi | ||
| done | ||
| done | ||
|
|
||
| kubectl set env deployment/strimzi-cluster-operator -n strimzi-operator \ | ||
| STRIMZI_NAMESPACE='kafka-staging,kafka-production' | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just have strimzi watch all namespaces cluster-wide?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why have strimzi watch all namespaces cluster wide when these are the only namespaces the tutorial will use? Not being facetious, genuinely curious what that achieves? |
||
|
|
||
| info "Waiting for Strimzi operator to restart..." | ||
| kubectl rollout status deployment/strimzi-cluster-operator -n strimzi-operator --timeout=120s | ||
|
|
||
| # ─── Step 4: Seed the Gitea repository with the overlay structure ───────────── | ||
|
|
||
| info "Resetting Gitea repository to lesson-2 starting state..." | ||
|
|
||
| WORK_DIR=$(mktemp -d) | ||
| trap cleanup EXIT | ||
|
|
||
| git clone "http://${GITEA_USER}:${GITEA_PASSWORD}@localhost:${GITEA_HOST_PORT}/${GITEA_USER}/${GITEA_REPO}.git" "${WORK_DIR}/repo" 2>/dev/null | ||
|
|
||
| rm -rf "${WORK_DIR}/repo/manifests" | ||
| mkdir -p "${WORK_DIR}/repo/manifests" | ||
| cp -r "${SCRIPT_DIR}/lesson-manifests/." "${WORK_DIR}/repo/manifests/" | ||
|
|
||
| pushd "${WORK_DIR}/repo" >/dev/null | ||
| git add . | ||
| if git diff --cached --quiet; then | ||
| info "Gitea repo is already in lesson-2 starting state, skipping commit." | ||
| else | ||
| git -c user.name="Tutorial Setup" -c user.email="setup@tutorial.local" commit -m "Reset to lesson-2 starting state" | ||
| git push | ||
| info "Lesson-2 starting state pushed to Gitea." | ||
| fi | ||
| TARGET_REVISION=$(git rev-parse HEAD) | ||
| popd >/dev/null | ||
|
|
||
| # ─── Step 5: Create ArgoCD Applications for staging and production ───────────── | ||
|
|
||
| info "Creating ArgoCD applications for staging and production..." | ||
|
|
||
| kubectl apply -f - <<EOF | ||
| apiVersion: argoproj.io/v1alpha1 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why both apps are defined inside shell script?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, I'll move them out into separate yaml files. thanks! |
||
| kind: Application | ||
| metadata: | ||
| name: kafka-staging | ||
| namespace: argocd | ||
| spec: | ||
| project: default | ||
| source: | ||
| repoURL: http://gitea-http.gitea.svc:3000/tutorial-user/streamshub-gitops.git | ||
| targetRevision: main | ||
| path: manifests/overlays/staging | ||
| destination: | ||
| server: https://kubernetes.default.svc | ||
| namespace: kafka-staging | ||
| syncPolicy: | ||
| automated: | ||
| prune: true | ||
| selfHeal: true | ||
| managedNamespaceMetadata: | ||
| labels: | ||
| argocd.argoproj.io/managed-by: argocd | ||
| syncOptions: | ||
| - CreateNamespace=true | ||
| EOF | ||
|
|
||
| kubectl apply -f - <<EOF | ||
| apiVersion: argoproj.io/v1alpha1 | ||
| kind: Application | ||
| metadata: | ||
| name: kafka-production | ||
| namespace: argocd | ||
| spec: | ||
| project: default | ||
| source: | ||
| repoURL: http://gitea-http.gitea.svc:3000/tutorial-user/streamshub-gitops.git | ||
| targetRevision: main | ||
| path: manifests/overlays/production | ||
| destination: | ||
| server: https://kubernetes.default.svc | ||
| namespace: kafka-production | ||
| syncPolicy: | ||
| automated: | ||
| prune: true | ||
| selfHeal: true | ||
| managedNamespaceMetadata: | ||
| labels: | ||
| argocd.argoproj.io/managed-by: argocd | ||
| syncOptions: | ||
| - CreateNamespace=true | ||
| EOF | ||
|
|
||
| # ─── Step 6: Wait for both applications to sync ─────────────────────────────── | ||
|
|
||
| info "Waiting for ArgoCD to sync both applications..." | ||
|
|
||
| kubectl annotate application kafka-staging -n argocd argocd.argoproj.io/refresh=normal --overwrite >/dev/null 2>&1 || true | ||
| kubectl annotate application kafka-production -n argocd argocd.argoproj.io/refresh=normal --overwrite >/dev/null 2>&1 || true | ||
|
|
||
| for app in kafka-staging kafka-production; do | ||
| SYNC_STATUS="Unknown" | ||
| for i in $(seq 1 24); do | ||
| CURRENT_REVISION=$(kubectl get application "${app}" -n argocd -o jsonpath='{.status.sync.revision}' 2>/dev/null || echo "") | ||
| SYNC_STATUS=$(kubectl get application "${app}" -n argocd -o jsonpath='{.status.sync.status}' 2>/dev/null || echo "Unknown") | ||
| if [[ "${CURRENT_REVISION}" == "${TARGET_REVISION}" && "${SYNC_STATUS}" == "Synced" ]]; then | ||
| break | ||
| fi | ||
| sleep 5 | ||
| done | ||
|
|
||
| if [[ "${SYNC_STATUS}" != "Synced" ]]; then | ||
| warn "Application '${app}' has not synced yet (status: ${SYNC_STATUS})." | ||
| warn "Check with: kubectl get application ${app} -n argocd" | ||
| else | ||
| info "Application '${app}' is synced." | ||
| fi | ||
| done | ||
|
|
||
| info "Waiting for Kafka clusters to be ready (this may take a few minutes)..." | ||
| kubectl wait kafka/my-cluster --for=condition=Ready -n kafka-staging --timeout=600s 2>/dev/null & | ||
| kubectl wait kafka/my-cluster --for=condition=Ready -n kafka-production --timeout=600s 2>/dev/null & | ||
| wait | ||
|
|
||
| # ─── Step 7: Print starting instructions ────────────────────────────────────── | ||
|
|
||
| ARGOCD_PASSWORD=$(b64decode "$(kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath='{.data.password}')") | ||
|
|
||
| echo "" | ||
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | ||
| echo "" | ||
| info "Lesson 2 is ready. Open README.md and follow the lesson steps." | ||
| echo "" | ||
| echo " Clone the repo to follow along:" | ||
| echo " git clone http://${GITEA_USER}:${GITEA_PASSWORD}@localhost:${GITEA_HOST_PORT}/${GITEA_USER}/${GITEA_REPO}.git /tmp/gitops-lesson-2" | ||
| echo "" | ||
| echo " Gitea (your Git server): http://localhost:${GITEA_HOST_PORT}" | ||
| echo " Username: ${GITEA_USER} Password: ${GITEA_PASSWORD}" | ||
| echo "" | ||
| echo " ArgoCD Dashboard (open in a separate terminal):" | ||
| echo " kubectl port-forward svc/argocd-server -n argocd 8080:443" | ||
| echo " URL: https://localhost:8080" | ||
| echo " Username: admin" | ||
| echo " Password: ${ARGOCD_PASSWORD}" | ||
| echo "" | ||
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I said before lets put all these constants in a common script