├── .gitignore ├── .gitkeep ├── LICENSE ├── README.md ├── chapter02 ├── Docker_Hello_World │ ├── app.py │ ├── dockerfile │ ├── first-cd-pipeline-deployment.yaml │ └── requirements.txt └── hello-world-deployment.yaml ├── chapter03 ├── dockerfile └── requirements.txt ├── chapter04 ├── argocd_gitops │ ├── .github │ │ └── workflows │ │ │ └── build-and-push-image.yml │ ├── argocd-deployment.yaml │ ├── deployment │ │ ├── deployment.yaml │ │ └── service.yaml │ └── src │ │ ├── data.csv │ │ ├── data.csv.bak │ │ ├── dockerfile │ │ ├── index.html │ │ ├── package.json │ │ └── server.js ├── gitops-k8s-deployments-helm │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── deployment.yaml │ │ ├── hpa.yaml │ │ ├── ingress.yaml │ │ ├── service.yaml │ │ ├── serviceaccount.yaml │ │ └── tests │ │ │ └── test-connection.yaml │ └── values.yaml └── gitops-k8s-deployments-kustomize │ ├── base │ ├── deployment.yaml │ ├── kustomization.yaml │ └── service.yaml │ └── overlays │ ├── development │ ├── kustomization.yaml │ └── patches │ │ └── deployment_patch.yaml │ └── staging │ ├── kustomization.yaml │ └── patches │ └── deployment_patch.yaml ├── chapter05 ├── chapter-5-building-a-service-catalog-for-kubernetes │ ├── applicationsets │ │ ├── argocd-applicationset.yaml │ │ ├── cert-mananger-applicationset.yaml │ │ ├── external-dns-applicationset.yaml │ │ └── nginx-ingress-applicationset.yaml │ ├── bonus │ │ ├── README.md │ │ ├── check-helm-dep-updates.sh │ │ ├── dependencies.yaml │ │ └── examples │ │ │ ├── dns │ │ │ └── external-dns │ │ │ │ └── Chart.yaml │ │ │ ├── security │ │ │ └── kyverno │ │ │ │ └── Chart.yaml │ │ │ └── storage │ │ │ ├── minio-operator │ │ │ └── Chart.yaml │ │ │ └── minio-tenant │ │ │ └── Chart.yaml │ ├── cluster │ │ ├── in-cluster │ │ │ ├── dns │ │ │ │ └── external-dns │ │ │ │ │ └── values.yaml │ │ │ ├── networking │ │ │ │ └── ingress-nginx │ │ │ │ │ └── values.yaml │ │ │ └── security │ │ │ │ └── cert-manager │ │ │ │ └── values.yaml │ │ └── vcluster-team-a │ │ │ ├── networkpolicy-deny-ingress.yaml │ │ │ ├── optimization │ │ │ └── vcluster │ │ │ │ └── values.yaml │ │ │ └── rbac.yaml │ ├── dns │ │ └── external-dns │ │ │ ├── Chart.yaml │ │ │ └── values.yaml │ ├── networking │ │ └── ingress-nginx │ │ │ ├── Chart.yaml │ │ │ └── values.yaml │ ├── optimization │ │ └── vcluster │ │ │ ├── Chart.yaml │ │ │ └── values.yaml │ ├── security │ │ ├── cert-manager │ │ │ ├── Chart.yaml │ │ │ └── values.yaml │ │ ├── external-secrets │ │ │ ├── Chart.yaml │ │ │ └── values.yaml │ │ └── kyverno │ │ │ ├── Chart.yaml │ │ │ └── values.yaml │ └── system │ │ └── argocd │ │ ├── .gitignore │ │ ├── Chart.yaml │ │ ├── templates │ │ └── ingress.yaml │ │ └── values.yaml ├── chapter-5-effective-git-repository-strategies │ ├── environment-branches │ │ └── simple-webapp │ │ │ ├── prod-deployment.yaml │ │ │ ├── qa-deployment.yaml │ │ │ └── stage-deployment.yaml │ └── folders-for-environments │ │ ├── base │ │ ├── deployment.yaml │ │ └── kustomization.yaml │ │ └── overlays │ │ ├── prod │ │ ├── kustomization.yaml │ │ └── patch.yaml │ │ ├── qa │ │ ├── kustomization.yaml │ │ └── patch.yaml │ │ └── stage │ │ ├── kustomization.yaml │ │ └── patch.yaml ├── chapter-5-multitenancy-with-vcluster-and-argo-cd │ ├── bonus │ │ └── connect_vcluster_fleet.sh │ └── devteam-a │ │ └── vcluster-application.yaml ├── chapter-5-native-multitenancy-with-argo-cd │ └── devteam-a │ │ ├── application-initializer.yaml │ │ ├── argocd-project-devteam-a.yaml │ │ ├── gitrepository-sealed.yaml │ │ ├── namespace.yaml │ │ ├── networkpolicy-deny-ingress.yaml │ │ ├── rbac.yaml │ │ └── resource-quotas.yaml ├── chapter-5-scale-with-applicationset-generators │ └── nginx-ingress-applicationset-example │ │ └── nginx-ingress-applicationset.yaml └── chapter-5-the-app-of-apps-approach │ ├── app-of-app │ ├── application.yaml │ └── simple-webapp │ │ ├── deployment.yaml │ │ └── service.yaml │ ├── app-of-apps │ ├── application.yaml │ └── simple-webapps │ │ ├── simple-webapp-1 │ │ ├── deployment.yaml │ │ └── service.yaml │ │ ├── simple-webapp-2 │ │ ├── deployment.yaml │ │ └── service.yaml │ │ └── simple-webapp-3 │ │ ├── deployment.yaml │ │ └── service.yaml │ └── applicationsets │ ├── simple-webapp-applicationset.yaml │ └── simple-webapp │ ├── deployment.yaml │ ├── namespace.yaml │ └── service.yaml ├── chapter06 └── chapter-6-centralized-kubernetes-cluster-creation │ ├── README.md │ └── capi-quickstart.yaml ├── chapter08 ├── azure-pipelines.yml ├── deployment │ ├── base │ │ ├── deployment.yaml │ │ └── service.yaml │ └── kustomization.yaml ├── iac │ ├── aws │ │ └── main.tf │ └── azure │ │ ├── main.tf │ │ └── versions.tf ├── manifests │ ├── deployment.yml │ └── service.yml └── src │ ├── data.csv │ ├── dockerfile │ ├── index.html │ ├── package.json │ └── server.js ├── chapter09 ├── azure-pipelines.yml ├── deployment │ ├── base │ │ ├── deployment.yaml │ │ └── service.yaml │ └── kustomization.yaml ├── iac │ ├── aws │ │ └── main.tf │ └── azure │ │ ├── main.tf │ │ └── versions.tf ├── manifests │ ├── deployment.yml │ └── service.yml └── src │ ├── data.csv │ ├── dockerfile │ ├── index.html │ ├── package.json │ └── server.js ├── chapter10 ├── Docker │ └── dockerfile ├── README.md ├── flux-gitops-definitions │ ├── dev-iac-automation.yaml │ ├── github-repository-definition.yaml │ ├── github-repository-secret.yaml │ ├── prod-iac-automation.yaml │ └── staging-iac-automation.yaml ├── iac │ └── azure │ │ └── vnet │ │ └── main.tf └── multi-env │ └── iac │ └── azure │ ├── base │ ├── main.tf │ ├── readme.md │ └── variables.tf │ ├── dev │ ├── main.tf │ └── variables.tf │ ├── prod │ ├── main.tf │ └── variables.tf │ └── staging │ ├── main.tf │ └── variables.tf ├── chapter11 ├── Step-01 │ ├── ArgoCD-GitOps │ │ └── argocd_deployment.yaml │ ├── backend-api.py │ ├── deployment │ │ ├── backend-api-deployment.yaml │ │ └── backend-api-secrets.yaml │ ├── dockerfile │ ├── requirements.txt │ ├── scripts │ │ ├── get-argo-cd-external-ip.sh │ │ └── get-argo-cd-initial-password.sh │ └── terraform │ │ ├── main.tf │ │ └── variables.tf ├── Step-02-ArgoCD-Deployment │ └── gitops-for-real-ci-cd-pipeline.yml ├── Step-03-Scalability │ ├── deployment │ │ ├── backend-api-deployment.yaml │ │ └── hpa.yaml │ └── hpa-testing.sh ├── Step-04-Security │ ├── weather-app-manager-role-binding.yaml │ ├── weather-app-manager-role.yaml │ ├── weather-app-operator-role-binding.yaml │ └── weather-app-operator-role.yaml └── github │ └── workflows │ └── gitops-for-real-ci-cd-pipeline.yml ├── chapter13 ├── chapter-13-committing-everything-to-git-and-what-about-secrets │ ├── applicationsets │ │ └── security │ │ │ ├── external-secrets-applicationset.yaml │ │ │ └── sealed-secrets-applicationset.yaml │ ├── kustomize │ │ └── .gitexplodee │ └── security │ │ ├── external-secrets │ │ ├── Chart.yaml │ │ └── values.yaml │ │ └── sealed-secrets │ │ ├── Chart.yaml │ │ └── values.yaml ├── chapter-13-hardening-declarative-gitops-cd-on-kubernetes │ └── end_user_threat_model.pdf └── chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices │ ├── applicationsets │ └── security │ │ └── kyverno-applicationset.yaml │ ├── kustomize │ ├── .gitexplodee │ └── security │ │ └── kyverno │ │ └── policies │ │ └── base │ │ ├── argocd-application-field-validation.yaml │ │ ├── argocd-application-prevent-default-project.yaml │ │ ├── argocd-application-prevent-updates-project.yaml │ │ ├── disallow-container-sock-mounts.yaml │ │ ├── disallow-empty-ingress-host.yaml │ │ ├── disallow-latest-tag.yaml │ │ ├── drop-all-capabilities.yaml │ │ ├── drop-cap-net-raw.yaml │ │ ├── kustomization.yaml │ │ ├── require-labels.yaml │ │ ├── require-pod-probes.yaml │ │ ├── require-requests-limits.yaml │ │ ├── require-ro-rootfs.yaml │ │ ├── restrict-deprecated-registry.yaml │ │ └── restrict-nodeport.yaml │ └── security │ └── kyverno │ ├── Chart.yaml │ └── values.yaml ├── chapter14 └── chapter-14-forecasting-and-monitoring-costs-with-gitops │ ├── applicationsets │ └── optimization │ │ └── opencost-applicationset.yaml │ ├── cluster │ ├── in-cluster-austria │ │ └── optimization │ │ │ └── opencost │ │ │ └── values.yaml │ ├── in-cluster-germany │ │ └── optimization │ │ │ └── opencost │ │ │ └── values.yaml │ └── in-cluster-ireland │ │ └── optimization │ │ └── opencost │ │ └── values.yaml │ └── optimization │ ├── kubecost │ ├── Chart.yaml │ ├── README.md │ ├── images │ │ ├── kubecost_ui_budget_setup_alert.gif │ │ └── kubecost_ui_setup_alert.gif │ └── values.yaml │ └── opencost │ ├── Chart.yaml │ └── values.yaml └── pod-gitops-terraform-automation-tf-runner.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Helm 2 | charts/ 3 | Chart.lock 4 | 5 | # Docker 6 | .DS_Store 7 | .dockerignore 8 | docker-compose.yml 9 | Dockerfile 10 | 11 | # Kubernetes 12 | *.env 13 | 14 | # macOS 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Implementing-GitOps-with-Kubernetes/123557c4905e8677fc4a6807aee28e146dcc192e/.gitkeep -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Implementing GitOps with Kubernetes 2 | 3 | Implementing GitOps with Kubernetes 4 | 5 | This is the code repository for [Implementing GitOps with Kubernetes](https://www.packtpub.com/en-in/product/implementing-gitops-with-kubernetes-9781835884225?utm_source=github&utm_medium=repository&utm_campaign=9781786461629), published by Packt. 6 | 7 | **Automate, manage, scale, and secure infrastructure and cloud-native applications on AWS and Azure** 8 | 9 | ## What is this book about? 10 | This book provides step-by-step tutorials and hands-on examples for effectively implementing GitOps practices in your Kubernetes deployments. You’ll learn how to automate, monitor, and secure your infrastructure for efficient application delivery. 11 | 12 | 13 | This book covers the following exciting features: 14 | * Delve into GitOps methods and best practices used for modern cloud-native environments 15 | * Explore GitOps tools such as GitHub, Argo CD, Flux CD, Helm, and Kustomize 16 | * Automate Kubernetes CI/CD workflows using GitOps and GitHub Actions 17 | * Deploy infrastructure as code using Terraform, OpenTofu, and GitOps 18 | * Automate AWS, Azure, and OpenShift platforms with GitOps 19 | * Understand multitenancy, rolling back deployments, and how to handle stateful applications using GitOps methods 20 | * Implement observability, security, cost optimization, and AI in GitOps practices 21 | 22 | If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1835884237) today! 23 | 24 | https://www.packtpub.com/ 26 | 27 | ## Instructions and Navigations 28 | All of the code is organized into folders. For example, Chapter02. 29 | 30 | The code will look like the following: 31 | ``` 32 | helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets 33 | helm install sealed-secrets sealed-secrets/sealed-secrets 34 | #Install e.g. CLI on MacOS 35 | brew install kubeseal 36 | ``` 37 | 38 | **Following is what you need for this book:** 39 | This book is for DevOps engineers, platform engineers, SREs, and cloud engineers who want to get skilled at implementing GitOps practices effectively in cloud-native environments. A foundational understanding of cloud computing, containerization, infrastructure as code, DevOps, CI/CD principles, and Kubernetes will be helpful to get the most out of this book. 40 | 41 | With the following software and hardware list you can run all code files present in the book (Chapter 1-14). 42 | ### Software and Hardware List 43 | | Chapter | Software required | OS required | 44 | | -------- | ------------------------------------ | ----------------------------------- | 45 | | 1-14 | Kubernetes | Windows, Mac OS X, and Linux (Any) | 46 | | 1-14 | Git | Windows, Mac OS X, and Linux (Any) | 47 | | 1-14 | Docker | Windows, Mac OS X, and Linux (Any) | 48 | | 1-14 | Argo CD | Windows, Mac OS X, and Linux (Any) | 49 | | 1-14 | Flux CD | Windows, Mac OS X, and Linux (Any) | 50 | | 1-14 | Helm | Windows, Mac OS X, and Linux (Any) | 51 | | 1-14 | Kustomize | Windows, Mac OS X, and Linux (Any) | 52 | | 1-14 | Terraform | Windows, Mac OS X, and Linux (Any) | 53 | | 1-14 | Azure Kubernetes Service (AKS) | Windows, Mac OS X, and Linux (Any) | 54 | | 1-14 | AWS Elastic Kubernetes Service (EKS) | Windows, Mac OS X, and Linux (Any) | 55 | | 1-14 | OpenShift | Windows, Mac OS X, and Linux (Any) | 56 | 57 | 58 | ### Related products 59 | * Automating DevOps with GitLab CI/CD Pipelines [[Packt]](https://www.packtpub.com/en-in/product/automating-devops-with-gitlab-cicd-pipelines-9781803233000?utm_source=github&utm_medium=repository&utm_campaign=) [[Amazon]](https://www.amazon.com/dp/1803233001) 60 | 61 | * Modern DevOps Practices [[Packt]](https://www.packtpub.com/en-in/product/modern-devops-practices-9781805121824?utm_source=github&utm_medium=repository&utm_campaign=) [[Amazon]](https://www.amazon.com/dp/1805121820) 62 | 63 | ## Get to Know the Author 64 | **Pietro Libro** 65 | is a tech enthusiast with over two decades of experience in software development and software architecture. His pragmatic problem-solving skills have been honed through work in the public administration, finance, and automation industries. He holds a master’s degree in computer science from the University of Rome, La Sapienza. Over the years, Pietro has transitioned from software development to a solution and cloud architect role. He is currently awaiting the defense of his PhD in bioinformatics at the University of Tuscia. Pietro’s dedication to learning is evident through his numerous certifications and his role as a technical speaker. Specializing in various technologies, especially software and cloud architecture, he relocated from Italy to Switzerland. Currently serving as a cloud solution architect in Zürich, Pietro lives with his wife, Eleonora, his daughter, Giulia, and their cat, “Miau”. In his free time, Pietro enjoys biking, practicing taekwondo, watching science fiction movies and series, and spending time with his family. 66 | 67 | 68 | **Artem Lajko** 69 | is a passionate and driven platform engineer and Kubstronaut, boasting over eight years of IT experience, backed by a master’s degree in computer science. His track record showcases expertise in designing, developing, and deploying efficient and scalable cloud infrastructures. As a curious and continuous learner, Artem holds certifications in Azure, AWS, Kubernetes, and GitOps. Currently, he’s playing a pivotal role in enhancing innovation and application management at the Port of Hamburg. His technical acumen spans cloud infrastructures, cross-cloud solutions, and DevOps practices. He is also passionate about blogging and networking with manufacturers to craft top-notch solutions using market-available tools. 70 | 71 | -------------------------------------------------------------------------------- /chapter02/Docker_Hello_World/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | app = Flask(__name__) 3 | 4 | @app.route('/') 5 | def hello_world(): 6 | return 'Hello, World!\n' 7 | 8 | @app.route('/name/') 9 | def hello_name(name): 10 | return 'Hello, {}\n'.format(name) 11 | 12 | @app.route('/datetime') 13 | def datetime(): 14 | import datetime 15 | now = datetime.datetime.now() 16 | return now.strftime("%Y-%m-%d %H:%M:%S\n") 17 | 18 | if __name__ == '__main__': 19 | app.run(debug=True, host='0.0.0.0', port=80) 20 | 21 | -------------------------------------------------------------------------------- /chapter02/Docker_Hello_World/dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Python runtime as a parent image 2 | FROM python:3.8-slim 3 | 4 | # Set the working directory in the container 5 | WORKDIR /usr/src/app 6 | 7 | # Copy the current directory contents into the container at /usr/src/app 8 | COPY . . 9 | 10 | # Install any needed packages specified in requirements.txt 11 | RUN pip install --no-cache-dir -r requirements.txt 12 | 13 | # Make port 80 available to the world outside this container 14 | EXPOSE 80 15 | 16 | # Define environment variable 17 | ENV NAME World 18 | 19 | # Run app.py when the container launches 20 | CMD ["python", "app.py"] 21 | -------------------------------------------------------------------------------- /chapter02/Docker_Hello_World/first-cd-pipeline-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: first-cd-pipeline-deployment 5 | namespace: gitops-kubernetes 6 | labels: 7 | app: first-cd-pipeline 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: first-cd-pipeline 13 | template: 14 | metadata: 15 | labels: 16 | app: first-cd-pipeline 17 | spec: 18 | containers: 19 | - name: first-cd-pipeline 20 | image: pietrolibro/hello-world-py-app:2.0 21 | ports: 22 | - containerPort: 80 23 | --- 24 | apiVersion: v1 25 | kind: Service 26 | metadata: 27 | name: first-cd-pipeline-service 28 | namespace: gitops-kubernetes 29 | spec: 30 | type: NodePort 31 | selector: 32 | app: first-cd-pipeline 33 | ports: 34 | - protocol: TCP 35 | port: 80 36 | nodePort: 30007 37 | -------------------------------------------------------------------------------- /chapter02/Docker_Hello_World/requirements.txt: -------------------------------------------------------------------------------- 1 | blinker==1.6.3 2 | click==8.1.7 3 | colorama==0.4.6 4 | distlib==0.3.7 5 | filelock==3.12.3 6 | Flask==3.0.0 7 | itsdangerous==2.1.2 8 | Jinja2>=3.1.4 9 | MarkupSafe==2.1.3 10 | platformdirs==3.10.0 11 | virtualenv==20.24.5 12 | Werkzeug>=3.0.3 -------------------------------------------------------------------------------- /chapter02/hello-world-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: hello-world-deployment 5 | namespace: gitops-kubernetes 6 | labels: 7 | app: hello-world 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: hello-world 13 | template: 14 | metadata: 15 | labels: 16 | app: hello-world 17 | spec: 18 | containers: 19 | - name: hello-world 20 | image: nginxdemos/hello 21 | ports: 22 | - containerPort: 80 23 | --- 24 | apiVersion: v1 25 | kind: Service 26 | metadata: 27 | name: hello-world-service 28 | namespace: gitops-kubernetes 29 | spec: 30 | type: NodePort 31 | selector: 32 | app: hello-world 33 | ports: 34 | - protocol: TCP 35 | port: 80 36 | nodePort: 30007 37 | -------------------------------------------------------------------------------- /chapter03/dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Python runtime as a parent image 2 | FROM python:3.8-slim 3 | 4 | # Set the working directory in the container 5 | WORKDIR /usr/src/app 6 | 7 | # Copy the current directory contents into the container at /usr/src/app 8 | COPY . . 9 | 10 | # Install any needed packages specified in requirements.txt 11 | RUN pip install --no-cache-dir -r requirements.txt 12 | 13 | # Make port 80 available to the world outside this container 14 | EXPOSE 80 15 | 16 | # Define environment variable 17 | ENV NAME World 18 | 19 | # Run app.py when the container launches 20 | CMD ["python", "app.py"] 21 | -------------------------------------------------------------------------------- /chapter03/requirements.txt: -------------------------------------------------------------------------------- 1 | blinker==1.6.3 2 | click==8.1.7 3 | colorama==0.4.6 4 | distlib==0.3.7 5 | filelock==3.12.3 6 | Flask==3.0.0 7 | itsdangerous==2.1.2 8 | Jinja2>=3.1.4 9 | MarkupSafe==2.1.3 10 | platformdirs==3.10.0 11 | virtualenv==20.24.5 12 | Werkzeug>=3.0.3 -------------------------------------------------------------------------------- /chapter04/argocd_gitops/.github/workflows/build-and-push-image.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker image 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | - 'release' 8 | 9 | jobs: 10 | push_to_registry: 11 | name: Push Docker image to Docker Hub 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Check out the repo 15 | uses: actions/checkout@v4 16 | 17 | - name: Log in to Docker Hub 18 | uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a 19 | with: 20 | username: ${{ secrets.DOCKER_USERNAME }} 21 | password: ${{ secrets.DOCKER_PASSWORD }} 22 | 23 | - name: Extract metadata (tags, labels) for Docker 24 | id: meta 25 | uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 26 | with: 27 | images: pietrolibro/gitops-k8s-deployments 28 | 29 | - name: Build and push Docker image 30 | uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 31 | with: 32 | context: . 33 | file: ./src/dockerfile 34 | push: true 35 | tags: ${{ steps.meta.outputs.tags }} 36 | labels: ${{ steps.meta.outputs.labels }} 37 | -------------------------------------------------------------------------------- /chapter04/argocd_gitops/argocd-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: my-city-weahter-app 5 | namespace: argocd 6 | spec: 7 | destination: 8 | namespace: gitops-kubernetes 9 | server: https://kubernetes.default.svc 10 | project: default 11 | source: 12 | repoURL: https://github.com/pietrolibro/gitops-k8s-deployments-book-weather-app.git 13 | path: deployment 14 | targetRevision: main -------------------------------------------------------------------------------- /chapter04/argocd_gitops/deployment/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: zurich-weather-app 5 | labels: 6 | app: zurich-weather-app 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: zurich-weather-app 12 | template: 13 | metadata: 14 | labels: 15 | app: zurich-weather-app 16 | spec: 17 | containers: 18 | - name: zurich-weather-app 19 | image: pietrolibro/zurich-weather-app:1.0 20 | ports: 21 | - containerPort: 8080 22 | # resources: 23 | # requests: 24 | # memory: "64Mi" 25 | # cpu: "250m" 26 | # limits: 27 | # memory: "128Mi" 28 | # cpu: "500m" 29 | -------------------------------------------------------------------------------- /chapter04/argocd_gitops/deployment/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: zurich-weather-app-service 5 | spec: 6 | type: LoadBalancer 7 | ports: 8 | - port: 80 9 | targetPort: 8080 10 | protocol: TCP 11 | selector: 12 | app: zurich-weather-app -------------------------------------------------------------------------------- /chapter04/argocd_gitops/src/data.csv: -------------------------------------------------------------------------------- 1 | Date,Temperature 2 | 2023-01-01,5 3 | 2023-01-02,6 4 | 2023-01-03,4 5 | 2023-01-04,4 6 | 2023-01-05,5 7 | 2023-01-06,5 8 | 2023-01-07,6 9 | 2023-01-08,6 10 | 2023-01-09,7 11 | 2023-01-10,6 12 | 2023-01-11,7 13 | -------------------------------------------------------------------------------- /chapter04/argocd_gitops/src/data.csv.bak: -------------------------------------------------------------------------------- 1 | Date,Temperature 2 | 2023-01-01,5 3 | 2023-01-02,6 4 | 2023-01-03,4 5 | 2023-01-04,3 6 | 2023-01-05,4 7 | 2023-01-06,5 8 | 2023-01-07,6 9 | 2023-01-08,7 10 | 2023-01-09,8 11 | 2023-01-10,6 12 | 2023-01-11,7 13 | -------------------------------------------------------------------------------- /chapter04/argocd_gitops/src/dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Node.js runtime as a parent image 2 | FROM node:14 3 | 4 | # Set the working directory 5 | WORKDIR /usr/src/app 6 | 7 | # Copy package.json and install dependencies 8 | COPY package*.json ./ 9 | RUN npm install 10 | 11 | # Bundle app source 12 | COPY . . 13 | 14 | # Expose the port the app runs on 15 | EXPOSE 3000 16 | 17 | # Define the command to run the app 18 | CMD ["node", "server.js"] 19 | -------------------------------------------------------------------------------- /chapter04/argocd_gitops/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Temperature Chart 5 | 6 | 7 | 8 | 9 | 10 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /chapter04/argocd_gitops/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "temperature-chart-app", 3 | "version": "1.0.0", 4 | "description": "A simple web app displaying temperature charts from CSV data", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "dependencies": { 10 | "express": "^4.17.1" 11 | }, 12 | "author": "", 13 | "license": "ISC" 14 | } 15 | -------------------------------------------------------------------------------- /chapter04/argocd_gitops/src/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const port = 3000; 4 | 5 | app.use(express.static('.')); // Serve static files from the current directory 6 | 7 | app.listen(port, () => { 8 | console.log(`Server running at http://localhost:${port}`); 9 | }); 10 | -------------------------------------------------------------------------------- /chapter04/gitops-k8s-deployments-helm/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /chapter04/gitops-k8s-deployments-helm/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: gitops-k8s-deployments-helm 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.2.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "2.1.0" 25 | -------------------------------------------------------------------------------- /chapter04/gitops-k8s-deployments-helm/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "gitops-k8s-deployments-helm.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "gitops-k8s-deployments-helm.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "gitops-k8s-deployments-helm.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "gitops-k8s-deployments-helm.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 20 | echo "Visit http://127.0.0.1:8080 to use your application" 21 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /chapter04/gitops-k8s-deployments-helm/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "gitops-k8s-deployments-helm.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "gitops-k8s-deployments-helm.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "gitops-k8s-deployments-helm.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "gitops-k8s-deployments-helm.labels" -}} 37 | helm.sh/chart: {{ include "gitops-k8s-deployments-helm.chart" . }} 38 | {{ include "gitops-k8s-deployments-helm.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "gitops-k8s-deployments-helm.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "gitops-k8s-deployments-helm.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "gitops-k8s-deployments-helm.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "gitops-k8s-deployments-helm.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /chapter04/gitops-k8s-deployments-helm/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "gitops-k8s-deployments-helm.fullname" . }} 5 | labels: 6 | {{- include "gitops-k8s-deployments-helm.labels" . | nindent 4 }} 7 | spec: 8 | {{- if not .Values.autoscaling.enabled }} 9 | replicas: {{ .Values.replicaCount }} 10 | {{- end }} 11 | selector: 12 | matchLabels: 13 | {{- include "gitops-k8s-deployments-helm.selectorLabels" . | nindent 6 }} 14 | template: 15 | metadata: 16 | {{- with .Values.podAnnotations }} 17 | annotations: 18 | {{- toYaml . | nindent 8 }} 19 | {{- end }} 20 | labels: 21 | {{- include "gitops-k8s-deployments-helm.labels" . | nindent 8 }} 22 | {{- with .Values.podLabels }} 23 | {{- toYaml . | nindent 8 }} 24 | {{- end }} 25 | spec: 26 | {{- with .Values.imagePullSecrets }} 27 | imagePullSecrets: 28 | {{- toYaml . | nindent 8 }} 29 | {{- end }} 30 | serviceAccountName: {{ include "gitops-k8s-deployments-helm.serviceAccountName" . }} 31 | securityContext: 32 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 33 | containers: 34 | - name: {{ .Chart.Name }} 35 | securityContext: 36 | {{- toYaml .Values.securityContext | nindent 12 }} 37 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 38 | imagePullPolicy: {{ .Values.image.pullPolicy }} 39 | ports: 40 | - name: http 41 | containerPort: {{ .Values.service.port }} 42 | protocol: TCP 43 | livenessProbe: 44 | {{- toYaml .Values.livenessProbe | nindent 12 }} 45 | readinessProbe: 46 | {{- toYaml .Values.readinessProbe | nindent 12 }} 47 | resources: 48 | {{- toYaml .Values.resources | nindent 12 }} 49 | {{- with .Values.volumeMounts }} 50 | volumeMounts: 51 | {{- toYaml . | nindent 12 }} 52 | {{- end }} 53 | {{- with .Values.volumes }} 54 | volumes: 55 | {{- toYaml . | nindent 8 }} 56 | {{- end }} 57 | {{- with .Values.nodeSelector }} 58 | nodeSelector: 59 | {{- toYaml . | nindent 8 }} 60 | {{- end }} 61 | {{- with .Values.affinity }} 62 | affinity: 63 | {{- toYaml . | nindent 8 }} 64 | {{- end }} 65 | {{- with .Values.tolerations }} 66 | tolerations: 67 | {{- toYaml . | nindent 8 }} 68 | {{- end }} 69 | -------------------------------------------------------------------------------- /chapter04/gitops-k8s-deployments-helm/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ include "gitops-k8s-deployments-helm.fullname" . }} 6 | namespace: {{ .Values.namespace }} 7 | labels: 8 | {{- include "gitops-k8s-deployments-helm.labels" . | nindent 4 }} 9 | spec: 10 | scaleTargetRef: 11 | apiVersion: apps/v1 12 | kind: Deployment 13 | name: {{ include "gitops-k8s-deployments-helm.fullname" . }} 14 | minReplicas: {{ .Values.autoscaling.minReplicas }} 15 | maxReplicas: {{ .Values.autoscaling.maxReplicas }} 16 | metrics: 17 | {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} 18 | - type: Resource 19 | resource: 20 | name: cpu 21 | target: 22 | type: Utilization 23 | averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} 24 | {{- end }} 25 | {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} 26 | - type: Resource 27 | resource: 28 | name: memory 29 | target: 30 | type: Utilization 31 | averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} 32 | {{- end }} 33 | {{- end }} 34 | -------------------------------------------------------------------------------- /chapter04/gitops-k8s-deployments-helm/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "gitops-k8s-deployments-helm.fullname" . -}} 3 | {{- $svcPort := .Values.service.port -}} 4 | {{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} 5 | {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} 6 | {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} 7 | {{- end }} 8 | {{- end }} 9 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} 10 | apiVersion: networking.k8s.io/v1 11 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 12 | apiVersion: networking.k8s.io/v1beta1 13 | {{- else -}} 14 | apiVersion: extensions/v1beta1 15 | {{- end }} 16 | kind: Ingress 17 | metadata: 18 | name: {{ $fullName }} 19 | labels: 20 | {{- include "gitops-k8s-deployments-helm.labels" . | nindent 4 }} 21 | {{- with .Values.ingress.annotations }} 22 | annotations: 23 | {{- toYaml . | nindent 4 }} 24 | {{- end }} 25 | spec: 26 | {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 27 | ingressClassName: {{ .Values.ingress.className }} 28 | {{- end }} 29 | {{- if .Values.ingress.tls }} 30 | tls: 31 | {{- range .Values.ingress.tls }} 32 | - hosts: 33 | {{- range .hosts }} 34 | - {{ . | quote }} 35 | {{- end }} 36 | secretName: {{ .secretName }} 37 | {{- end }} 38 | {{- end }} 39 | rules: 40 | {{- range .Values.ingress.hosts }} 41 | - host: {{ .host | quote }} 42 | http: 43 | paths: 44 | {{- range .paths }} 45 | - path: {{ .path }} 46 | {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} 47 | pathType: {{ .pathType }} 48 | {{- end }} 49 | backend: 50 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 51 | service: 52 | name: {{ $fullName }} 53 | port: 54 | number: {{ $svcPort }} 55 | {{- else }} 56 | serviceName: {{ $fullName }} 57 | servicePort: {{ $svcPort }} 58 | {{- end }} 59 | {{- end }} 60 | {{- end }} 61 | {{- end }} 62 | -------------------------------------------------------------------------------- /chapter04/gitops-k8s-deployments-helm/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "gitops-k8s-deployments-helm.fullname" . }} 5 | labels: 6 | {{- include "gitops-k8s-deployments-helm.labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.port }} 11 | targetPort: http 12 | protocol: TCP 13 | name: http 14 | selector: 15 | {{- include "gitops-k8s-deployments-helm.selectorLabels" . | nindent 4 }} -------------------------------------------------------------------------------- /chapter04/gitops-k8s-deployments-helm/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "gitops-k8s-deployments-helm.serviceAccountName" . }} 6 | labels: 7 | {{- include "gitops-k8s-deployments-helm.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | automountServiceAccountToken: {{ .Values.serviceAccount.automount }} 13 | {{- end }} 14 | -------------------------------------------------------------------------------- /chapter04/gitops-k8s-deployments-helm/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "gitops-k8s-deployments-helm.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "gitops-k8s-deployments-helm.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "gitops-k8s-deployments-helm.fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /chapter04/gitops-k8s-deployments-helm/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for gitops-k8s-deployments-helm. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: pietrolibro/hello-world-py-app 9 | pullPolicy: IfNotPresent 10 | # Overrides the image tag whose default is the chart appVersion. 11 | tag: 2.0 12 | 13 | imagePullSecrets: [] 14 | nameOverride: "" 15 | fullnameOverride: "" 16 | 17 | serviceAccount: 18 | # Specifies whether a service account should be created 19 | create: true 20 | # Automatically mount a ServiceAccount's API credentials? 21 | automount: true 22 | # Annotations to add to the service account 23 | annotations: {} 24 | # The name of the service account to use. 25 | # If not set and create is true, a name is generated using the fullname template 26 | name: "" 27 | 28 | podAnnotations: {} 29 | podLabels: {} 30 | 31 | podSecurityContext: {} 32 | # fsGroup: 2000 33 | 34 | securityContext: {} 35 | # capabilities: 36 | # drop: 37 | # - ALL 38 | # readOnlyRootFilesystem: true 39 | # runAsNonRoot: true 40 | # runAsUser: 1000 41 | 42 | service: 43 | type: ClusterIP 44 | port: 80 45 | 46 | ingress: 47 | enabled: false 48 | className: "" 49 | annotations: {} 50 | # kubernetes.io/ingress.class: nginx 51 | # kubernetes.io/tls-acme: "true" 52 | hosts: 53 | - host: chart-example.local 54 | paths: 55 | - path: / 56 | pathType: ImplementationSpecific 57 | tls: [] 58 | # - secretName: chart-example-tls 59 | # hosts: 60 | # - chart-example.local 61 | 62 | resources: {} 63 | # We usually recommend not to specify default resources and to leave this as a conscious 64 | # choice for the user. This also increases chances charts run on environments with little 65 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 66 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 67 | # limits: 68 | # cpu: 100m 69 | # memory: 128Mi 70 | # requests: 71 | # cpu: 100m 72 | # memory: 128Mi 73 | 74 | livenessProbe: 75 | httpGet: 76 | path: / 77 | port: http 78 | readinessProbe: 79 | httpGet: 80 | path: / 81 | port: http 82 | 83 | autoscaling: 84 | enabled: false 85 | minReplicas: 1 86 | maxReplicas: 100 87 | targetCPUUtilizationPercentage: 80 88 | # targetMemoryUtilizationPercentage: 80 89 | 90 | # Additional volumes on the output Deployment definition. 91 | volumes: [] 92 | # - name: foo 93 | # secret: 94 | # secretName: mysecret 95 | # optional: false 96 | 97 | # Additional volumeMounts on the output Deployment definition. 98 | volumeMounts: [] 99 | # - name: foo 100 | # mountPath: "/etc/foo" 101 | # readOnly: true 102 | 103 | nodeSelector: {} 104 | 105 | tolerations: [] 106 | 107 | affinity: {} 108 | -------------------------------------------------------------------------------- /chapter04/gitops-k8s-deployments-kustomize/base/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: gitops-k8s-kustomize 5 | labels: 6 | app: gitops-k8s-kustomize 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: gitops-k8s-kustomize 12 | template: 13 | metadata: 14 | labels: 15 | app: gitops-k8s-kustomize 16 | spec: 17 | containers: 18 | - name: gitops-k8s-kustomize 19 | image: k8s.gcr.io/echoserver:1.10 20 | ports: 21 | - containerPort: 8080 22 | -------------------------------------------------------------------------------- /chapter04/gitops-k8s-deployments-kustomize/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - deployment.yaml 3 | - service.yaml -------------------------------------------------------------------------------- /chapter04/gitops-k8s-deployments-kustomize/base/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: gitops-k8s-kustomize 5 | spec: 6 | selector: 7 | app: gitops-k8s-kustomize 8 | ports: 9 | - protocol: TCP 10 | port: 80 11 | targetPort: 8080 12 | -------------------------------------------------------------------------------- /chapter04/gitops-k8s-deployments-kustomize/overlays/development/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: gitops-k8s-kustomize-dev 2 | resources: 3 | - ../../base 4 | patches: 5 | - path: ./patches/deployment_patch.yaml -------------------------------------------------------------------------------- /chapter04/gitops-k8s-deployments-kustomize/overlays/development/patches/deployment_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: gitops-k8s-kustomize 5 | labels: 6 | environment: development 7 | spec: 8 | replicas: 2 # Increase the number of replicas in development. 9 | -------------------------------------------------------------------------------- /chapter04/gitops-k8s-deployments-kustomize/overlays/staging/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: gitops-k8s-kustomize-stg 2 | resources: 3 | - ../../base 4 | patches: 5 | - path: ./patches/deployment_patch.yaml -------------------------------------------------------------------------------- /chapter04/gitops-k8s-deployments-kustomize/overlays/staging/patches/deployment_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: gitops-k8s-kustomize 5 | labels: 6 | environment: staging 7 | spec: 8 | replicas: 3 # Increase the number of replicas in staging. 9 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/applicationsets/argocd-applicationset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: ApplicationSet 3 | metadata: 4 | name: argocd 5 | namespace: argocd 6 | spec: 7 | generators: 8 | - clusters: 9 | selector: 10 | matchLabels: 11 | env: dev #cluster label 12 | core-basic: enabled 13 | values: 14 | branch: main #point to main branch 15 | - clusters: 16 | selector: 17 | matchLabels: 18 | env: prod #cluster label 19 | core-basic: enabled 20 | values: 21 | branch: main #point to main branch 22 | template: 23 | metadata: 24 | name: "{{name}}-argocd" #name of cluster + name of application 25 | annotations: 26 | argocd.argoproj.io/manifest-generate-paths: ".;.." 27 | spec: 28 | project: default 29 | sources: 30 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git #custom values repo 31 | targetRevision: main #point to main branch of custom values repo 32 | ref: valuesRepo #use valuesRepo variable 33 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git #umbrella repo 34 | targetRevision: "{{values.branch}}" #point to the branch depending on the cluster branch pointer 35 | path: "./chapter05/chapter-5-building-a-service-catalog-for-kubernetes/system/argocd" #path to the application in the umbrella repo 36 | helm: 37 | releaseName: "argocd" # Release name override (defaults to application name) 38 | valueFiles: 39 | - "values.yaml" #use values.yaml from umbrella repo 40 | - "$valuesRepo/chapter05/chapter-5-building-a-service-catalog-for-kubernetes/cluster/{{name}}/system/argocd/values.yaml" #override umbrella values from custom values repo 41 | destination: 42 | name: "{{name}}" 43 | namespace: "argocd" 44 | syncPolicy: 45 | automated: 46 | prune: false 47 | selfHeal: true 48 | syncOptions: 49 | - CreateNamespace=true 50 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/applicationsets/cert-mananger-applicationset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: ApplicationSet 3 | metadata: 4 | name: cert-manager 5 | namespace: argocd 6 | spec: 7 | generators: 8 | - clusters: 9 | selector: 10 | matchLabels: 11 | env: dev #cluster label 12 | core-basic: enabled 13 | security-basic: enabled 14 | values: 15 | branch: main #point to main branch 16 | - clusters: 17 | selector: 18 | matchLabels: 19 | env: prod #cluster label 20 | core-basic: enabled 21 | security-basic: enabled 22 | values: 23 | branch: main #point to main branch 24 | template: 25 | metadata: 26 | name: "{{name}}-cert-manager" #name of cluster + name of application 27 | annotations: 28 | argocd.argoproj.io/manifest-generate-paths: ".;.." 29 | spec: 30 | project: default 31 | sources: 32 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git #custom values repo 33 | targetRevision: main #point to main branch of custom values repo 34 | ref: valuesRepo #use valuesRepo variable 35 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git #umbrella repo 36 | targetRevision: "{{values.branch}}" #point to the branch depending on the cluster branch pointer 37 | path: "./chapter05/chapter-5-building-a-service-catalog-for-kubernetes/security/cert-manager" #path to the application in the umbrella repo 38 | helm: 39 | releaseName: "cert-manager" # Release name override (defaults to application name) 40 | valueFiles: 41 | - "values.yaml" #use values.yaml from umbrella repo 42 | - "$valuesRepo/chapter05/chapter-5-building-a-service-catalog-for-kubernetes/cluster/{{name}}/security/cert-manager/values.yaml" #override umbrella values from custom values repo 43 | destination: 44 | name: "{{name}}" 45 | namespace: "cert-manager" 46 | syncPolicy: 47 | automated: 48 | prune: false 49 | selfHeal: true 50 | syncOptions: 51 | - CreateNamespace=true 52 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/applicationsets/external-dns-applicationset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: ApplicationSet 3 | metadata: 4 | name: external-dns 5 | namespace: argocd 6 | spec: 7 | generators: 8 | - clusters: 9 | selector: 10 | matchLabels: 11 | env: dev #cluster label 12 | core-basic: enabled 13 | values: 14 | branch: main #point to main branch 15 | - clusters: 16 | selector: 17 | matchLabels: 18 | env: prod #cluster label 19 | core-basic: enabled 20 | values: 21 | branch: main #point to main branch 22 | template: 23 | metadata: 24 | name: "{{name}}-external-dns" #name of cluster + name of application 25 | annotations: 26 | argocd.argoproj.io/manifest-generate-paths: ".;.." 27 | spec: 28 | project: default 29 | sources: 30 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git #custom values repo 31 | targetRevision: main #point to main branch of custom values repo 32 | ref: valuesRepo #use valuesRepo variable 33 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git #umbrella repo 34 | targetRevision: "{{values.branch}}" #point to the branch depending on the cluster branch pointer 35 | path: "./chapter05/chapter-5-building-a-service-catalog-for-kubernetes/dns/external-dns" #path to the application in the umbrella repo 36 | helm: 37 | releaseName: "external-dns" # Release name override (defaults to application name) 38 | valueFiles: 39 | - "values.yaml" #use values.yaml from umbrella repo 40 | - "$valuesRepo/chapter-5-building-a-service-catalog-for-kubernetes/cluster/{{name}}/dns/external-dns/values.yaml" #override umbrella values from custom values repo 41 | destination: 42 | name: "{{name}}" 43 | namespace: "external-dns" 44 | syncPolicy: 45 | automated: 46 | prune: false 47 | selfHeal: true 48 | syncOptions: 49 | - CreateNamespace=true 50 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/applicationsets/nginx-ingress-applicationset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: ApplicationSet 3 | metadata: 4 | name: ingress-nginx 5 | namespace: argocd 6 | spec: 7 | generators: 8 | - clusters: 9 | selector: 10 | matchLabels: 11 | env: dev #cluster label 12 | core-basic: enabled 13 | values: 14 | branch: main #point to main branch 15 | - clusters: 16 | selector: 17 | matchLabels: 18 | env: prod #cluster label 19 | core-basic: enabled 20 | values: 21 | branch: main #point to main branch 22 | template: 23 | metadata: 24 | name: "{{name}}-ingress-nginx" #name of cluster + name of application 25 | annotations: 26 | argocd.argoproj.io/manifest-generate-paths: ".;.." 27 | spec: 28 | project: default 29 | sources: 30 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git #custom values repo 31 | targetRevision: main #point to main branch of custom values repo 32 | ref: valuesRepo #use valuesRepo variable 33 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git #umbrella repo 34 | targetRevision: "{{values.branch}}" #point to the branch depending on the cluster branch pointer 35 | path: "./chapter05/chapter-5-building-a-service-catalog-for-kubernetes/networking/ingress-nginx" #path to the application in the umbrella repo 36 | helm: 37 | releaseName: "ingress-nginx" # Release name override (defaults to application name) 38 | valueFiles: 39 | - "values.yaml" #use values.yaml from umbrella repo 40 | - "$valuesRepo/chapter05/chapter-5-building-a-service-catalog-for-kubernetes/cluster/{{name}}/networking/ingress-nginx/values.yaml" #override umbrella values from custom values repo 41 | destination: 42 | name: "{{name}}" 43 | namespace: "ingress-nginx" 44 | syncPolicy: 45 | automated: 46 | prune: false 47 | selfHeal: true 48 | syncOptions: 49 | - CreateNamespace=true 50 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/bonus/dependencies.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | BRANCH: main 3 | DRY_RUN: true 4 | GITHUB: false 5 | AZURE_DEVOPS: false 6 | WITHOUT_PR: false 7 | 8 | dependencies: 9 | - name: "External DNS" 10 | arrayPosition: 0 11 | repositoryName: bitnami/external-dns 12 | sourcePath: examples/dns/external-dns 13 | - name: "Kyverno" 14 | arrayPosition: 0 15 | repositoryName: kyverno/kyverno 16 | sourcePath: examples/security/kyverno 17 | - name: "minIO Operator" 18 | arrayPosition: 0 19 | repositoryName: minio-operator/operator 20 | sourcePath: examples/storage/minio-operator 21 | - name: "minIO Tenant" 22 | arrayPosition: 0 23 | repositoryName: minio-tenant/tenant 24 | sourcePath: examples/storage/minio-tenant 25 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/bonus/examples/dns/external-dns/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: external-dns 3 | version: 1.0.0 4 | description: This Chart deploys external-dns. 5 | dependencies: 6 | - name: external-dns 7 | version: 6.20.4 8 | repository: https://charts.bitnami.com/bitnami 9 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/bonus/examples/security/kyverno/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: kyverno 3 | version: 1.0.0 4 | description: This Chart deploys kyverno. 5 | dependencies: 6 | - name: kyverno 7 | version: 3.0.0 8 | repository: https://kyverno.github.io/kyverno/ 9 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/bonus/examples/storage/minio-operator/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: minio-operator 3 | version: 1.0.0 4 | description: This Chart deploys minio-operator. 5 | dependencies: 6 | - name: operator 7 | version: 5.0.1 8 | repository: https://operator.min.io/ -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/bonus/examples/storage/minio-tenant/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: minio-tenant 3 | version: 1.0.0 4 | description: This Chart deploys minio tenant. 5 | dependencies: 6 | - name: tenant 7 | version: 5.0.1 8 | repository: https://operator.min.io/ -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/cluster/in-cluster/dns/external-dns/values.yaml: -------------------------------------------------------------------------------- 1 | external-dns: 2 | domainFilters: 3 | - your-domain.com 4 | txtOwnerId: "in-cluster-dev" 5 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/cluster/in-cluster/networking/ingress-nginx/values.yaml: -------------------------------------------------------------------------------- 1 | ingress-nginx: 2 | controller: 3 | resources: 4 | requests: 5 | memory: 1000Mi 6 | cpu: 1000m 7 | limits: 8 | memory: 1500Mi 9 | cpu: 1500m 10 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/cluster/in-cluster/security/cert-manager/values.yaml: -------------------------------------------------------------------------------- 1 | cert-manager: 2 | resources: 3 | requests: 4 | cpu: 1000m 5 | memory: 1000Mi 6 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/cluster/vcluster-team-a/networkpolicy-deny-ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: networking.k8s.io/v1 3 | kind: NetworkPolicy 4 | metadata: 5 | name: default-deny-ingress 6 | namespace: vcluster-team-a 7 | spec: 8 | podSelector: {} 9 | policyTypes: 10 | - Ingress 11 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/cluster/vcluster-team-a/optimization/vcluster/values.yaml: -------------------------------------------------------------------------------- 1 | vcluster: 2 | sync: 3 | ingresses: 4 | enabled: true 5 | syncer: 6 | extraArgs: 7 | - --tls-san=vcluster-a.example.com 8 | ingress: 9 | enabled: true 10 | host: vcluster-a.example.com 11 | ingressClassName: "nginx" 12 | annotations: 13 | nginx.ingress.kubernetes.io/backend-protocol: HTTPS 14 | nginx.ingress.kubernetes.io/ssl-passthrough: "true" 15 | nginx.ingress.kubernetes.io/ssl-redirect: "true" 16 | isolation: 17 | podSecurityStandard: baseline 18 | # If enabled will add node/proxy permission to the cluster role 19 | # in isolation mode 20 | resourceQuota: 21 | enabled: true 22 | quota: 23 | requests.cpu: 10 24 | requests.memory: 20Gi 25 | requests.storage: "100Gi" 26 | requests.ephemeral-storage: 50Gi 27 | limits.cpu: 20 28 | limits.memory: 40Gi 29 | limits.ephemeral-storage: 150Gi 30 | services.nodeports: 1 31 | services.loadbalancers: 1 32 | count/endpoints: 40 33 | count/pods: 30 34 | count/services: 30 35 | count/secrets: 100 36 | count/configmaps: 100 37 | count/persistentvolumeclaims: 20 38 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/cluster/vcluster-team-a/rbac.yaml: -------------------------------------------------------------------------------- 1 | kind: RoleBinding 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | name: rolebinding-admin 5 | namespace: vcluster-team-a 6 | roleRef: 7 | kind: ClusterRole 8 | name: admin 9 | apiGroup: rbac.authorization.k8s.io 10 | subjects: 11 | - kind: Group 12 | name: sso:DEV_Team_K8s_Dev@example.com 13 | apiGroup: rbac.authorization.k8s.io 14 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/dns/external-dns/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: external-dns 3 | version: 1.0.0 4 | description: This Chart deploys external-dns. 5 | dependencies: 6 | - name: external-dns 7 | version: 6.26.1 8 | repository: https://charts.bitnami.com/bitnami 9 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/dns/external-dns/values.yaml: -------------------------------------------------------------------------------- 1 | external-dns: 2 | txtOwnerId: "overlay_me" 3 | 4 | rbac: 5 | pspEnabled: true 6 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/networking/ingress-nginx/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: ingress-nginx 3 | version: 1.0.0 4 | description: This Chart deploys ingress-nginx. 5 | dependencies: 6 | - name: ingress-nginx 7 | version: 4.8.0 8 | repository: https://kubernetes.github.io/ingress-nginx 9 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/networking/ingress-nginx/values.yaml: -------------------------------------------------------------------------------- 1 | ingress-nginx: 2 | controller: 3 | allowSnippetAnnotations: true 4 | resources: 5 | requests: 6 | memory: 250Mi 7 | cpu: 250m 8 | limits: 9 | memory: 500Mi 10 | cpu: 500m 11 | metrics: 12 | enabled: true 13 | serviceMonitor: 14 | enabled: false 15 | setAsDefaultIngress: true 16 | extraArgs: 17 | enable-ssl-passthrough: true 18 | defaultBackend: 19 | enabled: true 20 | resources: 21 | requests: 22 | memory: 50Mi 23 | cpu: 100m 24 | limits: 25 | memory: 100Mi 26 | cpu: 150m 27 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/optimization/vcluster/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: vcluster 3 | version: 1.0.0 4 | description: This Chart deploys vcluster k3s by default. 5 | #vcluster use default k3s 6 | dependencies: 7 | - name: vcluster 8 | version: 0.18.1 9 | repository: https://charts.loft.sh 10 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/optimization/vcluster/values.yaml: -------------------------------------------------------------------------------- 1 | vcluster: 2 | # Resource syncers that should be enabled/disabled. 3 | # Enabling syncers will impact RBAC Role and ClusterRole permissions. 4 | # To disable a syncer set "enabled: false". 5 | # See docs for details - https://www.vcluster.com/docs/architecture/synced-resources 6 | # Configure SecurityContext of the containers in the VCluster pod 7 | securityContext: 8 | runAsUser: 12345 9 | runAsGroup: 12345 10 | runAsNonRoot: true 11 | 12 | # Configure fsGroup 13 | # The field is specified, all processes of the container are also part of the supplementary group ID 12345 14 | fsGroup: 12345 15 | 16 | # If enabled will deploy vcluster in an isolated mode with pod security 17 | # standards, limit ranges and resource quotas 18 | isolation: 19 | enabled: true 20 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/security/cert-manager/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: cert-manager 3 | version: 1.0.0 4 | description: This Chart deploys cert-manager. 5 | dependencies: 6 | - name: cert-manager 7 | version: v1.13.0 8 | repository: https://charts.jetstack.io 9 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/security/cert-manager/values.yaml: -------------------------------------------------------------------------------- 1 | cert-manager: 2 | installCRDs: true 3 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/security/external-secrets/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: external-secrets 3 | version: 1.0.0 4 | description: This Chart deploys external-secrets secrets. 5 | dependencies: 6 | - name: external-secrets 7 | version: "0.9.5" 8 | repository: https://charts.external-secrets.io 9 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/security/external-secrets/values.yaml: -------------------------------------------------------------------------------- 1 | external-secrets: 2 | serviceMonitor: 3 | # -- Specifies whether to create a ServiceMonitor resource for collecting Prometheus metrics 4 | enabled: true 5 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/security/kyverno/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: kyverno 3 | version: 1.0.0 4 | description: This Chart deploys kyverno. 5 | dependencies: 6 | - name: kyverno 7 | version: 3.0.5 8 | repository: https://kyverno.github.io/kyverno/ 9 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/security/kyverno/values.yaml: -------------------------------------------------------------------------------- 1 | kyverno: 2 | # Admission controller configuration 3 | admissionController: 4 | replicas: 0 5 | # Reports controller configuration 6 | reportsController: 7 | replicas: 0 8 | # Cleanup controller configuration 9 | cleanupController: 10 | replicas: 0 11 | # Background controller configuration 12 | backgroundController: 13 | replicas: 0 14 | 15 | # CRDs configuration 16 | crds: 17 | # -- Whether to have Helm install the Kyverno CRDs, if the CRDs are not installed by Helm, they must be added before policies can be created 18 | install: true 19 | annotations: 20 | argocd.argoproj.io/sync-options: Replace=true 21 | strategy.spinnaker.io/replace: "true" 22 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/system/argocd/.gitignore: -------------------------------------------------------------------------------- 1 | charts/ 2 | Chart.lock 3 | test/ -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/system/argocd/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: argocd 3 | version: 1.0.0 4 | description: This Chart deploys argoc cd by default. 5 | dependencies: 6 | - name: argo-cd 7 | version: 5.51.6 8 | repository: https://argoproj.github.io/argo-helm 9 | -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/system/argocd/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress -}} 2 | apiVersion: networking.k8s.io/v1 3 | kind: Ingress 4 | metadata: 5 | name: argocd-server-ingress 6 | annotations: 7 | kubernetes.io/ingress.class: {{ .Values.ingress.className }} 8 | ingress.kubernetes.io/force-ssl-redirect: "true" 9 | nginx.ingress.kubernetes.io/ssl-passthrough: {{ .Values.ingress.sslPassthrough | default "false" | quote }} 10 | cert-manager.io/cluster-issuer: {{ .Values.ingress.issuer }} 11 | cert-manager.io/renew-before: 360h #15 days 12 | cert-manager.io/common-name: {{ .Values.ingress.host }} 13 | kubernetes.io/tls-acme: "true" 14 | nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" 15 | spec: 16 | rules: 17 | - host: {{ .Values.ingress.host }} 18 | http: 19 | paths: 20 | - path: / 21 | pathType: Prefix 22 | backend: 23 | service: 24 | name: argocd 25 | port: 26 | name: https 27 | tls: 28 | - hosts: 29 | - {{ .Values.ingress.host }} 30 | secretName: {{ .Values.ingress.secretName | default "argocd-secret" }} # do not change, this is provided by Argo CD 31 | {{- end -}} -------------------------------------------------------------------------------- /chapter05/chapter-5-building-a-service-catalog-for-kubernetes/system/argocd/values.yaml: -------------------------------------------------------------------------------- 1 | ingress: 2 | host: "argocd.yourdomain.com" 3 | issuer: "letsencrypt" 4 | className: "nginx" 5 | 6 | argo-cd: 7 | redis: 8 | create: true 9 | 10 | controller: 11 | replicas: 1 12 | 13 | server: 14 | replicas: 2 15 | 16 | repoServer: 17 | replicas: 2 18 | 19 | applicationSet: 20 | replicaCount: 2 21 | -------------------------------------------------------------------------------- /chapter05/chapter-5-effective-git-repository-strategies/environment-branches/simple-webapp/prod-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: simple-webapp 5 | spec: 6 | replicas: 3 7 | selector: 8 | matchLabels: 9 | app: simple-webapp 10 | template: 11 | metadata: 12 | labels: 13 | app: simple-webapp 14 | spec: 15 | containers: 16 | - image: "ghcr.io/la-cc/simple-webapp:1.0.1" 17 | name: simple-webapp 18 | -------------------------------------------------------------------------------- /chapter05/chapter-5-effective-git-repository-strategies/environment-branches/simple-webapp/qa-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: simple-webapp 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: simple-webapp 10 | template: 11 | metadata: 12 | labels: 13 | app: simple-webapp 14 | spec: 15 | containers: 16 | - image: "ghcr.io/la-cc/simple-webapp:1.0.1" 17 | name: simple-webapp 18 | -------------------------------------------------------------------------------- /chapter05/chapter-5-effective-git-repository-strategies/environment-branches/simple-webapp/stage-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: simple-webapp 5 | spec: 6 | replicas: 2 7 | selector: 8 | matchLabels: 9 | app: simple-webapp 10 | template: 11 | metadata: 12 | labels: 13 | app: simple-webapp 14 | spec: 15 | containers: 16 | - image: "ghcr.io/la-cc/simple-webapp:1.0.1" 17 | name: simple-webapp 18 | -------------------------------------------------------------------------------- /chapter05/chapter-5-effective-git-repository-strategies/folders-for-environments/base/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: simple-webapp 5 | spec: 6 | replicas: 2 7 | selector: 8 | matchLabels: 9 | app: simple-webapp 10 | template: 11 | metadata: 12 | labels: 13 | app: simple-webapp 14 | spec: 15 | containers: 16 | - image: "ghcr.io/la-cc/simple-webapp:1.1.0-stable" 17 | name: simple-webapp 18 | env: 19 | - name: UI_X_COLOR 20 | value: darkblue 21 | - name: SUBSCRIPTION_TIER #business-related values 22 | value: silver 23 | -------------------------------------------------------------------------------- /chapter05/chapter-5-effective-git-repository-strategies/folders-for-environments/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - deployment.yaml 3 | -------------------------------------------------------------------------------- /chapter05/chapter-5-effective-git-repository-strategies/folders-for-environments/overlays/prod/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../../base 5 | patches: 6 | - path: patch.yaml 7 | namePrefix: prod- 8 | commonLabels: 9 | variant: prod 10 | -------------------------------------------------------------------------------- /chapter05/chapter-5-effective-git-repository-strategies/folders-for-environments/overlays/prod/patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: simple-webapp 5 | spec: 6 | replicas: 1 7 | template: 8 | spec: 9 | containers: 10 | - image: "ghcr.io/la-cc/simple-webapp:1.1.0-prod" 11 | name: simple-webapp 12 | env: 13 | - name: UI_X_COLOR 14 | value: premium 15 | -------------------------------------------------------------------------------- /chapter05/chapter-5-effective-git-repository-strategies/folders-for-environments/overlays/qa/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../../base 5 | patches: 6 | - path: patch.yaml 7 | namePrefix: qa- 8 | commonLabels: 9 | variant: qa 10 | -------------------------------------------------------------------------------- /chapter05/chapter-5-effective-git-repository-strategies/folders-for-environments/overlays/qa/patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: simple-webapp 5 | spec: 6 | replicas: 1 7 | template: 8 | spec: 9 | containers: 10 | - image: "ghcr.io/la-cc/simple-webapp:1.1.5-new-ui" 11 | name: simple-webapp 12 | env: 13 | - name: UI_X_COLOR 14 | value: aqua 15 | -------------------------------------------------------------------------------- /chapter05/chapter-5-effective-git-repository-strategies/folders-for-environments/overlays/stage/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../../base 5 | patches: 6 | - path: patch.yaml 7 | namePrefix: stage- 8 | commonLabels: 9 | variant: stage 10 | -------------------------------------------------------------------------------- /chapter05/chapter-5-effective-git-repository-strategies/folders-for-environments/overlays/stage/patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: simple-webapp 5 | spec: 6 | replicas: 2 7 | template: 8 | spec: 9 | containers: 10 | - image: "ghcr.io/la-cc/simple-webapp:1.1.4-feature-login" 11 | name: simple-webapp 12 | -------------------------------------------------------------------------------- /chapter05/chapter-5-multitenancy-with-vcluster-and-argo-cd/bonus/connect_vcluster_fleet.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Check if context is provided 4 | if [ -z "$1" ]; then 5 | echo "You forgot to set the context!" 6 | echo "you can set the context with connectVcluster CONTEXT!" 7 | echo "e.g. connectVcluster sunrise-development" 8 | exit 1 9 | fi 10 | 11 | # Attempt to switch to the context passed as argument 12 | kubectl config use-context "$1" &>/dev/null 13 | 14 | # Check the exit status of the previous command 15 | if [ $? -ne 0 ]; then 16 | echo "Invalid context: $1" 17 | echo "Maybe should connect first vs the guest-cluster or checking if you making a typing error?" 18 | exit 1 19 | fi 20 | 21 | namespace=$(mktemp) 22 | hostname=$(mktemp) 23 | vcluster=$(mktemp) 24 | 25 | kubectl get namespaces -o=jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' | grep vcluster >"$namespace" 26 | 27 | kubectl get namespaces -o=jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' | grep vcluster | kubectl get ing -A -o json | jq -r '.items[] | select(.spec.rules[].host | contains("vcluster")) | .spec.rules[0].host' >"$hostname" 28 | 29 | paste -d ' ' "$namespace" "$hostname" >"$vcluster" 30 | 31 | mapfile -t list <$vcluster 32 | 33 | # Iterate over the list 34 | for item in "${list[@]}"; do 35 | # splitting the element into arguments 36 | args=($item) 37 | 38 | # output of the arguments in the desired form 39 | vcluster connect vcluster -n ${args[0]} --server=https://${args[1]} --kube-config-context-name ${args[0]} --context $1 40 | done 41 | 42 | rm "$namespace" "$hostname" "$vcluster" 43 | -------------------------------------------------------------------------------- /chapter05/chapter-5-multitenancy-with-vcluster-and-argo-cd/devteam-a/vcluster-application.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: vcluster-team-a 5 | namespace: argocd 6 | spec: 7 | destination: 8 | name: "" 9 | namespace: vcluster-team-a 10 | server: "https://kubernetes.default.svc" 11 | sources: 12 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git 13 | targetRevision: main 14 | ref: valuesRepo 15 | # - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git 16 | # path: "./chapter05/chapter-5-building-a-service-catalog-for-kubernetes/cluster/vcluster-team-a/" 17 | # targetRevision: main 18 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git 19 | targetRevision: main 20 | path: "./chapter05/chapter-5-building-a-service-catalog-for-kubernetes/optimization/vcluster" 21 | helm: 22 | releaseName: "vcluster-team-a" 23 | valueFiles: 24 | - "values.yaml" 25 | - "$valuesRepo/chapter05/chapter-5-building-a-service-catalog-for-kubernetes/cluster/vcluster-team-a/optimization/vcluster/values.yaml" 26 | project: default 27 | syncPolicy: 28 | automated: 29 | prune: false 30 | selfHeal: true 31 | syncOptions: 32 | - CreateNamespace=true 33 | -------------------------------------------------------------------------------- /chapter05/chapter-5-native-multitenancy-with-argo-cd/devteam-a/application-initializer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: application-initializer-devteam-a 5 | namespace: argocd 6 | spec: 7 | project: devteam-a 8 | source: 9 | repoURL: https://dev.azure.com/ORGA-X/devteam-a/_git/application 10 | targetRevision: main 11 | path: ./applicationset 12 | destination: 13 | server: https://kubernetes.default.svc 14 | namespace: devteam-a 15 | # Sync policy 16 | syncPolicy: 17 | automated: 18 | selfHeal: true 19 | syncOptions: 20 | - Validate=false 21 | - PrunePropagationPolicy=foreground 22 | - PruneLast=true 23 | - FailOnSharedResource=true 24 | -------------------------------------------------------------------------------- /chapter05/chapter-5-native-multitenancy-with-argo-cd/devteam-a/argocd-project-devteam-a.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: AppProject 3 | metadata: 4 | name: devteam-a 5 | namespace: argocd 6 | # Finalizer that ensures that project is not deleted until it is not referenced by any application 7 | finalizers: 8 | - resources-finalizer.argocd.argoproj.io 9 | spec: 10 | # Project description 11 | description: Enable DevTeam-A Project to install new applications 12 | 13 | # Allow manifests to deploy from any Git repos 14 | sourceRepos: 15 | # Any other repo are restricted 16 | - "*" 17 | 18 | destinations: 19 | - namespace: "devteam-a" 20 | server: https://kubernetes.default.svc 21 | 22 | # Restrict Namespace cluster-scoped resources from being created 23 | clusterResourceBlacklist: 24 | - group: "" 25 | kind: "Namespace" 26 | 27 | # Restrict namespaced-scoped resources from being created 28 | namespaceResourceBlacklist: 29 | - group: "argoproj.io" 30 | kind: "AppProject" 31 | - group: "argoproj.io" 32 | kind: "Application" 33 | - group: "" 34 | kind: "ResourceQuota" 35 | - group: "networking.k8s.io" 36 | kind: "NetworkPolicy" 37 | -------------------------------------------------------------------------------- /chapter05/chapter-5-native-multitenancy-with-argo-cd/devteam-a/gitrepository-sealed.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: bitnami.com/v1alpha1 2 | kind: SealedSecret 3 | metadata: 4 | annotations: 5 | sealedsecrets.bitnami.com/namespace-wide: "true" 6 | name: application-devteam-a 7 | namespace: argocd 8 | spec: 9 | encryptedData: 10 | enableLfs: AQCMuK6ZdqBpszav4vA3K5F5kvoD2hCGr19ZoZYmhEsY6mRBPNVnhcFH5GD9pF6RqKQpvKSt18zh3difnbJ3czyQ5xeI1FmYpiuyeBmOEGfNe... 11 | forceHttpBasicAuth: AQBf5U79aldO4mAMe6ZEHFbFKGH6yXn54Ki5MZcJBJTVbsF0/mVgqge+6HVoowvKUoivqp5d01HjiBH7KFDDwF+R6Txo8sYW0XI... 12 | insecure: AQB4g1/T1IyOoRkl9igO5K3cG1SFOo1fub1EN2PYh9o0evG4Ynd6c9Ezlg4lz+sVORk1x7tCyHhQ2My6xbqhLx6e6Fze6A/6Hz/twpsa5AtIP... 13 | password: AQAjZSIynjRukkfTsfk7ihp/UD9s4Wpo0nPFEb1tUWUAYdic94ZHjwiNjS2Z4GyEB2QiKCboA6PqJVKpetsiiOXx+H/kQCnl1duyvkAOqoRH... 14 | url: AQAXsTLAn+8RBImuekH9KnigsRLo/dmxfiFkG+6adxLAxMqgYZwmEkZLDuMhvBvGi0F0LugREYoSsPRs3Cy+qQ+SF+3ie/Ew3AVfw7MDkYwmJgBVmF... 15 | username: AQA1RBHngkFyWm/Xec7OeQeT8siwbgLOF6flu2Pfgg9twE3Rdm6jzmkbpRypuqKjNsgJMUenCxG6mQMqmJaCo8zyM7kGnOt76j8YaoBhnqZm... 16 | template: 17 | metadata: 18 | annotations: 19 | sealedsecrets.bitnami.com/namespace-wide: "true" 20 | labels: 21 | argocd.argoproj.io/secret-type: repository 22 | name: application-devteam-a 23 | namespace: argocd 24 | -------------------------------------------------------------------------------- /chapter05/chapter-5-native-multitenancy-with-argo-cd/devteam-a/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: devteam-a 5 | -------------------------------------------------------------------------------- /chapter05/chapter-5-native-multitenancy-with-argo-cd/devteam-a/networkpolicy-deny-ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: networking.k8s.io/v1 3 | kind: NetworkPolicy 4 | metadata: 5 | name: default-deny-ingress 6 | namespace: devteam-a 7 | spec: 8 | podSelector: {} 9 | policyTypes: 10 | - Ingress 11 | -------------------------------------------------------------------------------- /chapter05/chapter-5-native-multitenancy-with-argo-cd/devteam-a/rbac.yaml: -------------------------------------------------------------------------------- 1 | kind: RoleBinding 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | name: rolebinding-admin 5 | namespace: devteam-a 6 | roleRef: 7 | kind: ClusterRole 8 | name: admin 9 | apiGroup: rbac.authorization.k8s.io 10 | subjects: 11 | - kind: Group 12 | name: sso:DevTeam-A@your_domain 13 | apiGroup: rbac.authorization.k8s.io 14 | -------------------------------------------------------------------------------- /chapter05/chapter-5-native-multitenancy-with-argo-cd/devteam-a/resource-quotas.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ResourceQuota 3 | metadata: 4 | name: resource-quotas-ns-devteam-a 5 | namespace: devteam-a 6 | spec: 7 | hard: 8 | limits.cpu: "20" 9 | limits.ephemeral-storage: 100Gi 10 | limits.memory: 40Gi 11 | requests.cpu: "10" 12 | requests.ephemeral-storage: 50Gi 13 | requests.memory: 20Gi 14 | requests.storage: 100Gi 15 | services.loadbalancers: "1" 16 | pods: "20" 17 | -------------------------------------------------------------------------------- /chapter05/chapter-5-scale-with-applicationset-generators/nginx-ingress-applicationset-example/nginx-ingress-applicationset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: ApplicationSet 3 | metadata: 4 | name: ingress-nginx 5 | namespace: argocd 6 | spec: 7 | generators: 8 | - clusters: 9 | selector: 10 | matchLabels: 11 | env: dev #cluster label 12 | values: 13 | branch: main #point to main branch 14 | - clusters: 15 | selector: 16 | matchLabels: 17 | env: prod #cluster label 18 | values: 19 | branch: main #point to main branch 20 | template: 21 | metadata: 22 | name: "{{name}}-ingress-nginx" #name of cluster + name of application 23 | annotations: 24 | argocd.argoproj.io/manifest-generate-paths: ".;.." 25 | spec: 26 | project: default 27 | sources: 28 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git #custom values repo 29 | targetRevision: main #point to main branch of custom values repo 30 | ref: valuesRepo #use valuesRepo variable 31 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git #umbrella repo 32 | targetRevision: "{{values.branch}}" #point to the branch depending on the cluster branch pointer 33 | path: "./chapter05/chapter-5-building-a-service-catalog-for-kubernetes/networking/ingress-nginx" #path to the application in the umbrella repo 34 | helm: 35 | releaseName: "ingress-nginx" # Release name override (defaults to application name) 36 | valueFiles: 37 | - "values.yaml" #use values.yaml from umbrella repo 38 | - "$valuesRepo/chapter05/chapter-5-building-a-service-catalog-for-kubernetes/cluster/{{name}}/networking/ingress-nginx/values.yaml" #override umbrella values from custom values repo 39 | destination: 40 | name: "{{name}}" 41 | namespace: "ingress-nginx" 42 | syncPolicy: 43 | automated: 44 | prune: false 45 | selfHeal: true 46 | syncOptions: 47 | - CreateNamespace=true 48 | -------------------------------------------------------------------------------- /chapter05/chapter-5-the-app-of-apps-approach/app-of-app/application.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: app-of-app 5 | namespace: argocd 6 | finalizers: 7 | - resources-finalizer.argocd.argoproj.io 8 | spec: 9 | project: default 10 | source: 11 | repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git 12 | targetRevision: main 13 | path: ./chapter05/chapter-5-the-app-of-apps-approach/chapter-5-the-app-of-apps-approach/app-of-app/simple-webapp 14 | directory: 15 | recurse: false 16 | destination: 17 | server: https://kubernetes.default.svc 18 | namespace: app-of-app 19 | syncPolicy: 20 | syncOptions: 21 | - CreateNamespace=true 22 | automated: 23 | prune: true 24 | selfHeal: true 25 | -------------------------------------------------------------------------------- /chapter05/chapter-5-the-app-of-apps-approach/app-of-app/simple-webapp/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: simple-webapp 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: simple-webapp 10 | template: 11 | metadata: 12 | labels: 13 | app: simple-webapp 14 | spec: 15 | containers: 16 | - image: "ghcr.io/la-cc/simple-webapp:1.0.1" 17 | name: simple-webapp 18 | resources: 19 | requests: 20 | memory: "64Mi" 21 | cpu: "250m" 22 | limits: 23 | memory: "512Mi" 24 | cpu: "500m" 25 | ports: 26 | - containerPort: 8080 27 | securityContext: 28 | runAsUser: 1000 29 | dnsPolicy: ClusterFirst 30 | restartPolicy: Always 31 | -------------------------------------------------------------------------------- /chapter05/chapter-5-the-app-of-apps-approach/app-of-app/simple-webapp/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: simple-webapp-clusterip 5 | spec: 6 | selector: 7 | app: simple-webapp 8 | ports: 9 | - port: 80 10 | targetPort: 8080 11 | -------------------------------------------------------------------------------- /chapter05/chapter-5-the-app-of-apps-approach/app-of-apps/application.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: app-of-apps 5 | namespace: argocd 6 | finalizers: 7 | - resources-finalizer.argocd.argoproj.io 8 | spec: 9 | project: default 10 | source: 11 | repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git 12 | targetRevision: main 13 | path: ./chapter05/chapter-5-the-app-of-apps-approach/app-of-apps/simple-webapps 14 | directory: 15 | recurse: true 16 | destination: 17 | server: https://kubernetes.default.svc 18 | namespace: app-of-apps 19 | syncPolicy: 20 | syncOptions: 21 | - CreateNamespace=true 22 | automated: 23 | prune: true 24 | selfHeal: true 25 | -------------------------------------------------------------------------------- /chapter05/chapter-5-the-app-of-apps-approach/app-of-apps/simple-webapps/simple-webapp-1/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: simple-webapp-1 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: simple-webapp-1 10 | template: 11 | metadata: 12 | labels: 13 | app: simple-webapp-1 14 | spec: 15 | containers: 16 | - image: "ghcr.io/la-cc/simple-webapp:1.0.1" 17 | name: simple-webapp-1 18 | resources: 19 | requests: 20 | memory: "64Mi" 21 | cpu: "250m" 22 | limits: 23 | memory: "512Mi" 24 | cpu: "500m" 25 | ports: 26 | - containerPort: 8080 27 | securityContext: 28 | runAsUser: 1000 29 | dnsPolicy: ClusterFirst 30 | restartPolicy: Always 31 | -------------------------------------------------------------------------------- /chapter05/chapter-5-the-app-of-apps-approach/app-of-apps/simple-webapps/simple-webapp-1/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: simple-webapp-1-clusterip 5 | spec: 6 | selector: 7 | app: simple-webapp-1 8 | ports: 9 | - port: 80 10 | targetPort: 8080 11 | -------------------------------------------------------------------------------- /chapter05/chapter-5-the-app-of-apps-approach/app-of-apps/simple-webapps/simple-webapp-2/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: simple-webapp-2 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: simple-webapp-2 10 | template: 11 | metadata: 12 | labels: 13 | app: simple-webapp-2 14 | spec: 15 | containers: 16 | - image: "ghcr.io/la-cc/simple-webapp:1.0.1" 17 | name: simple-webapp-2 18 | resources: 19 | requests: 20 | memory: "64Mi" 21 | cpu: "250m" 22 | limits: 23 | memory: "512Mi" 24 | cpu: "500m" 25 | ports: 26 | - containerPort: 8080 27 | securityContext: 28 | runAsUser: 1000 29 | dnsPolicy: ClusterFirst 30 | restartPolicy: Always 31 | -------------------------------------------------------------------------------- /chapter05/chapter-5-the-app-of-apps-approach/app-of-apps/simple-webapps/simple-webapp-2/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: simple-webapp-2-clusterip 5 | spec: 6 | selector: 7 | app: simple-webapp-2 8 | ports: 9 | - port: 80 10 | targetPort: 8080 11 | -------------------------------------------------------------------------------- /chapter05/chapter-5-the-app-of-apps-approach/app-of-apps/simple-webapps/simple-webapp-3/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: simple-webapp-3 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: simple-webapp-3 10 | template: 11 | metadata: 12 | labels: 13 | app: simple-webapp-3 14 | spec: 15 | containers: 16 | - image: "ghcr.io/la-cc/simple-webapp:1.0.1" 17 | name: simple-webapp-3 18 | resources: 19 | requests: 20 | memory: "64Mi" 21 | cpu: "250m" 22 | limits: 23 | memory: "512Mi" 24 | cpu: "500m" 25 | ports: 26 | - containerPort: 8080 27 | securityContext: 28 | runAsUser: 1000 29 | dnsPolicy: ClusterFirst 30 | restartPolicy: Always 31 | -------------------------------------------------------------------------------- /chapter05/chapter-5-the-app-of-apps-approach/app-of-apps/simple-webapps/simple-webapp-3/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: simple-webapp-3-clusterip 5 | spec: 6 | selector: 7 | app: simple-webapp-3 8 | ports: 9 | - port: 80 10 | targetPort: 8080 11 | -------------------------------------------------------------------------------- /chapter05/chapter-5-the-app-of-apps-approach/applicationsets/simple-webapp-applicationset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: ApplicationSet 3 | metadata: 4 | name: simple-webapp 5 | namespace: argocd 6 | spec: 7 | generators: 8 | - clusters: 9 | selector: 10 | matchLabels: 11 | env: prod 12 | values: 13 | branch: main 14 | template: 15 | metadata: 16 | name: "{{name}}-simple-webapp" 17 | annotations: 18 | argocd.argoproj.io/manifest-generate-paths: ".;.." 19 | spec: 20 | project: default 21 | sources: 22 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git 23 | targetRevision: "{{values.branch}}" 24 | path: ./chapter05/chapter-5-the-app-of-apps-approach/app-of-app/simple-webapp 25 | destination: 26 | name: "{{name}}" 27 | namespace: "argocd" 28 | syncPolicy: 29 | automated: 30 | prune: false 31 | selfHeal: true 32 | syncOptions: 33 | - CreateNamespace=true 34 | -------------------------------------------------------------------------------- /chapter05/chapter-5-the-app-of-apps-approach/applicationsets/simple-webapp/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: simple-webapp 5 | namespace: simple-webapp 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: simple-webapp 11 | template: 12 | metadata: 13 | labels: 14 | app: simple-webapp 15 | spec: 16 | containers: 17 | - image: "ghcr.io/la-cc/simple-webapp:1.0.1" 18 | name: simple-webapp 19 | resources: 20 | requests: 21 | memory: "64Mi" 22 | cpu: "250m" 23 | limits: 24 | memory: "512Mi" 25 | cpu: "500m" 26 | ports: 27 | - containerPort: 8080 28 | securityContext: 29 | runAsUser: 1000 30 | dnsPolicy: ClusterFirst 31 | restartPolicy: Always 32 | -------------------------------------------------------------------------------- /chapter05/chapter-5-the-app-of-apps-approach/applicationsets/simple-webapp/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: simple-webapp 5 | -------------------------------------------------------------------------------- /chapter05/chapter-5-the-app-of-apps-approach/applicationsets/simple-webapp/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: simple-webapp-clusterip 5 | namespace: simple-webapp 6 | spec: 7 | selector: 8 | app: simple-webapp 9 | ports: 10 | - port: 80 11 | targetPort: 8080 12 | -------------------------------------------------------------------------------- /chapter06/chapter-6-centralized-kubernetes-cluster-creation/README.md: -------------------------------------------------------------------------------- 1 | # 0. Preparation steps 2 | 3 | ## Install the following tools: 4 | 5 | - kubectl 6 | - clusterctl 7 | - az 8 | - helm 9 | 10 | # 1. Initialize the management cluster 11 | 12 | export AZURE_SUBSCRIPTION_ID="" 13 | 14 | ## Create an Azure Service Principal and paste the output here 15 | 16 | export AZURE_TENANT_ID="" 17 | export AZURE_CLIENT_ID="" 18 | export AZURE_CLIENT_SECRET="" 19 | 20 | ## Base64 encode the variables 21 | 22 | export AZURE_SUBSCRIPTION_ID_B64="$(echo -n "$AZURE_SUBSCRIPTION_ID" | base64 | tr -d '\n')" 23 | export AZURE_TENANT_ID_B64="$(echo -n "$AZURE_TENANT_ID" | base64 | tr -d '\n')" 24 | export AZURE_CLIENT_ID_B64="$(echo -n "$AZURE_CLIENT_ID" | base64 | tr -d '\n')" 25 | export AZURE_CLIENT_SECRET_B64="$(echo -n "$AZURE_CLIENT_SECRET" | base64 | tr -d '\n')" 26 | 27 | ## Settings needed for AzureClusterIdentity used by the AzureCluster 28 | 29 | export AZURE_CLUSTER_IDENTITY_SECRET_NAME="cluster-identity-secret" 30 | export CLUSTER_IDENTITY_NAME="cluster-identity" 31 | export AZURE_CLUSTER_IDENTITY_SECRET_NAMESPACE="default" 32 | 33 | ## Create a secret to include the password of the Service Principal identity created in Azure 34 | 35 | ## This secret will be referenced by the AzureClusterIdentity used by the AzureCluster 36 | 37 | kubectl create secret generic "${AZURE_CLUSTER_IDENTITY_SECRET_NAME}" --from-literal=clientSecret="${AZURE_CLIENT_SECRET}" --namespace "${AZURE_CLUSTER_IDENTITY_SECRET_NAMESPACE}" 38 | 39 | ## Finally, initialize the management cluster 40 | 41 | clusterctl init --infrastructure azure 42 | 43 | # 2. Create your first workload cluster 44 | 45 | ## Name of the Azure datacenter location. Change this value to your desired location. 46 | 47 | export AZURE_LOCATION="westeurope" 48 | 49 | ## Select VM types. 50 | 51 | export AZURE_CONTROL_PLANE_MACHINE_TYPE="Standard_D2s_v3" 52 | export AZURE_NODE_MACHINE_TYPE="Standard_D2s_v3" 53 | 54 | ## [Optional] Select resource group. The default value is ${CLUSTER_NAME}. 55 | 56 | export AZURE_RESOURCE_GROUP="" 57 | 58 | ## Generating the cluster configuration 59 | 60 | clusterctl generate cluster capi-quickstart \ 61 | --kubernetes-version v1.29.0 \ 62 | --control-plane-machine-count=1 \ 63 | --worker-machine-count=3 \ 64 | > capi-quickstart.yaml 65 | 66 | # 3. Deploy the workload cluster 67 | 68 | _NOTE: You should do it the GitOps way._ 69 | 70 | kubectl apply -f capi-quickstart.yaml 71 | 72 | # 4. Deploy Calico CNI 73 | 74 | First, check if the kubeadmcontrolplane is ready: 75 | 76 | clusterctl describe cluster capi-quickstart 77 | 78 | You should get an output similar to this: 79 | 80 | NAME READY SEVERITY REASON SINCE MESSAGE 81 | Cluster/capi-quickstart True 11s 82 | ├─ClusterInfrastructure - AzureCluster/capi-quickstart True 2m34s 83 | ├─ControlPlane - KubeadmControlPlane/capi-quickstart-control-plane True 11s 84 | │ └─Machine/capi-quickstart-control-plane-dct9z True 12s 85 | └─Workers 86 | └─MachineDeployment/capi-quickstart-md-0 False Warning WaitingForAvailableMachines 5m4s Minimum availability requires 3 replicas, current 0 available 87 | └─3 Machines... False Info WaitingForBootstrapData 3s 88 | 89 | After the first control plane node is up and running, we can retrieve the workload cluster Kubeconfig. 90 | 91 | clusterctl get kubeconfig capi-quickstart > capi-quickstart.kubeconfig 92 | 93 | Now Install Calico CNI with: 94 | 95 | helm repo add projectcalico https://docs.tigera.io/calico/charts --kubeconfig=./capi-quickstart.kubeconfig && \ 96 | helm install calico projectcalico/tigera-operator --kubeconfig=./capi-quickstart.kubeconfig -f https://raw.githubusercontent.com/kubernetes-sigs/cluster-api-provider-azure/main/templates/addons/calico/values.yaml --namespace tigera-operator --create-namespace 97 | 98 | After a short while, nodes should be running and in Ready state, let’s check the status using kubectl get nodes 99 | 100 | kubectl get nodes --kubeconfig=./capi-quickstart.kubeconfig 101 | -------------------------------------------------------------------------------- /chapter06/chapter-6-centralized-kubernetes-cluster-creation/capi-quickstart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cluster.x-k8s.io/v1beta1 2 | kind: Cluster 3 | metadata: 4 | name: capi-quickstart 5 | namespace: default 6 | spec: 7 | clusterNetwork: 8 | pods: 9 | cidrBlocks: 10 | - 192.168.0.0/16 11 | controlPlaneRef: 12 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 13 | kind: KubeadmControlPlane 14 | name: capi-quickstart-control-plane 15 | infrastructureRef: 16 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 17 | kind: AzureCluster 18 | name: capi-quickstart 19 | --- 20 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 21 | kind: AzureCluster 22 | metadata: 23 | name: capi-quickstart 24 | namespace: default 25 | spec: 26 | identityRef: 27 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 28 | kind: AzureClusterIdentity 29 | name: cluster-identity 30 | location: westeurope 31 | networkSpec: 32 | subnets: 33 | - name: control-plane-subnet 34 | role: control-plane 35 | - name: node-subnet 36 | role: node 37 | vnet: 38 | name: capi-quickstart-vnet 39 | resourceGroup: capi-quickstart 40 | subscriptionID: 41 | --- 42 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 43 | kind: KubeadmControlPlane 44 | metadata: 45 | name: capi-quickstart-control-plane 46 | namespace: default 47 | spec: 48 | kubeadmConfigSpec: 49 | clusterConfiguration: 50 | apiServer: 51 | extraArgs: 52 | cloud-provider: external 53 | timeoutForControlPlane: 20m 54 | controllerManager: 55 | extraArgs: 56 | allocate-node-cidrs: "false" 57 | cloud-provider: external 58 | cluster-name: capi-quickstart 59 | etcd: 60 | local: 61 | dataDir: /var/lib/etcddisk/etcd 62 | extraArgs: 63 | quota-backend-bytes: "8589934592" 64 | diskSetup: 65 | filesystems: 66 | - device: /dev/disk/azure/scsi1/lun0 67 | extraOpts: 68 | - -E 69 | - lazy_itable_init=1,lazy_journal_init=1 70 | filesystem: ext4 71 | label: etcd_disk 72 | - device: ephemeral0.1 73 | filesystem: ext4 74 | label: ephemeral0 75 | replaceFS: ntfs 76 | partitions: 77 | - device: /dev/disk/azure/scsi1/lun0 78 | layout: true 79 | overwrite: false 80 | tableType: gpt 81 | files: 82 | - contentFrom: 83 | secret: 84 | key: control-plane-azure.json 85 | name: capi-quickstart-control-plane-azure-json 86 | owner: root:root 87 | path: /etc/kubernetes/azure.json 88 | permissions: "0644" 89 | initConfiguration: 90 | nodeRegistration: 91 | kubeletExtraArgs: 92 | azure-container-registry-config: /etc/kubernetes/azure.json 93 | cloud-provider: external 94 | name: '{{ ds.meta_data["local_hostname"] }}' 95 | joinConfiguration: 96 | nodeRegistration: 97 | kubeletExtraArgs: 98 | azure-container-registry-config: /etc/kubernetes/azure.json 99 | cloud-provider: external 100 | name: '{{ ds.meta_data["local_hostname"] }}' 101 | mounts: 102 | - - LABEL=etcd_disk 103 | - /var/lib/etcddisk 104 | postKubeadmCommands: [] 105 | preKubeadmCommands: [] 106 | machineTemplate: 107 | infrastructureRef: 108 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 109 | kind: AzureMachineTemplate 110 | name: capi-quickstart-control-plane 111 | replicas: 1 112 | version: v1.29.0 113 | --- 114 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 115 | kind: AzureMachineTemplate 116 | metadata: 117 | name: capi-quickstart-control-plane 118 | namespace: default 119 | spec: 120 | template: 121 | spec: 122 | dataDisks: 123 | - diskSizeGB: 256 124 | lun: 0 125 | nameSuffix: etcddisk 126 | osDisk: 127 | diskSizeGB: 128 128 | osType: Linux 129 | sshPublicKey: "" 130 | vmSize: Standard_D2s_v3 131 | --- 132 | apiVersion: cluster.x-k8s.io/v1beta1 133 | kind: MachineDeployment 134 | metadata: 135 | name: capi-quickstart-md-0 136 | namespace: default 137 | spec: 138 | clusterName: capi-quickstart 139 | replicas: 3 140 | selector: 141 | matchLabels: null 142 | template: 143 | spec: 144 | bootstrap: 145 | configRef: 146 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 147 | kind: KubeadmConfigTemplate 148 | name: capi-quickstart-md-0 149 | clusterName: capi-quickstart 150 | infrastructureRef: 151 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 152 | kind: AzureMachineTemplate 153 | name: capi-quickstart-md-0 154 | version: v1.29.0 155 | --- 156 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 157 | kind: AzureMachineTemplate 158 | metadata: 159 | name: capi-quickstart-md-0 160 | namespace: default 161 | spec: 162 | template: 163 | spec: 164 | osDisk: 165 | diskSizeGB: 128 166 | osType: Linux 167 | sshPublicKey: "" 168 | vmSize: Standard_D2s_v3 169 | --- 170 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 171 | kind: KubeadmConfigTemplate 172 | metadata: 173 | name: capi-quickstart-md-0 174 | namespace: default 175 | spec: 176 | template: 177 | spec: 178 | files: 179 | - contentFrom: 180 | secret: 181 | key: worker-node-azure.json 182 | name: capi-quickstart-md-0-azure-json 183 | owner: root:root 184 | path: /etc/kubernetes/azure.json 185 | permissions: "0644" 186 | joinConfiguration: 187 | nodeRegistration: 188 | kubeletExtraArgs: 189 | azure-container-registry-config: /etc/kubernetes/azure.json 190 | cloud-provider: external 191 | name: '{{ ds.meta_data["local_hostname"] }}' 192 | preKubeadmCommands: [] 193 | --- 194 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 195 | kind: AzureClusterIdentity 196 | metadata: 197 | labels: 198 | clusterctl.cluster.x-k8s.io/move-hierarchy: "true" 199 | name: cluster-identity 200 | namespace: default 201 | spec: 202 | allowedNamespaces: {} 203 | clientID: 204 | clientSecret: 205 | name: cluster-identity-secret 206 | namespace: default 207 | tenantID: 208 | type: ServicePrincipal 209 | -------------------------------------------------------------------------------- /chapter08/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - main 3 | 4 | resources: 5 | - repo: self 6 | 7 | variables: 8 | 9 | # Container registry service connection established during pipeline creation 10 | dockerRegistryServiceConnection: 'dd42c99b-7b18-42ae-9fe8-fa42ecd0bc04' 11 | imageRepository: 'myweatherappv1' 12 | containerRegistry: 'aksgitops3003204acr.azurecr.io' 13 | dockerfilePath: '**/src/dockerfile' 14 | tag: '$(Build.BuildId)' 15 | imagePullSecret: 'aksgitops3003204acra253-auth' 16 | 17 | # Agent VM image name 18 | vmImageName: 'ubuntu-latest' 19 | 20 | 21 | stages: 22 | - stage: Build 23 | displayName: Build stage 24 | jobs: 25 | - job: Build 26 | displayName: Build 27 | pool: 28 | vmImage: $(vmImageName) 29 | steps: 30 | - task: Docker@2 31 | displayName: Build and push an image to container registry 32 | inputs: 33 | command: buildAndPush 34 | repository: $(imageRepository) 35 | dockerfile: $(dockerfilePath) 36 | containerRegistry: $(dockerRegistryServiceConnection) 37 | tags: | 38 | $(tag) 39 | 40 | - upload: manifests 41 | artifact: manifests 42 | 43 | - stage: Deploy 44 | displayName: Deploy stage 45 | dependsOn: Build 46 | 47 | jobs: 48 | - deployment: Deploy 49 | displayName: Deploy 50 | pool: 51 | vmImage: $(vmImageName) 52 | environment: 'Development' 53 | strategy: 54 | runOnce: 55 | deploy: 56 | steps: 57 | - task: KubernetesManifest@0 58 | displayName: Create imagePullSecret 59 | inputs: 60 | action: createSecret 61 | secretName: $(imagePullSecret) 62 | dockerRegistryEndpoint: $(dockerRegistryServiceConnection) 63 | 64 | - task: KubernetesManifest@0 65 | displayName: Deploy to Kubernetes cluster 66 | inputs: 67 | action: deploy 68 | manifests: | 69 | $(Pipeline.Workspace)/manifests/deployment.yml 70 | $(Pipeline.Workspace)/manifests/service.yml 71 | imagePullSecrets: | 72 | $(imagePullSecret) 73 | containers: | 74 | $(containerRegistry)/$(imageRepository):$(tag) 75 | 76 | -------------------------------------------------------------------------------- /chapter08/deployment/base/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: my-city-weather-app 5 | # namespace: gitopsk8sdeployments 6 | labels: 7 | app: my-city-weather-app 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: my-city-weather-app 13 | template: 14 | metadata: 15 | labels: 16 | app: my-city-weather-app 17 | spec: 18 | containers: 19 | - name: my-city-weather-app 20 | image: 637423230075.dkr.ecr.eu-central-1.amazonaws.com/weather-app:latest 21 | ports: 22 | - containerPort: 8080 -------------------------------------------------------------------------------- /chapter08/deployment/base/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: my-city-weather-app-service 5 | # namespace: gitopsk8sdeployments 6 | spec: 7 | type: LoadBalancer 8 | ports: 9 | - port: 80 10 | targetPort: 8080 11 | protocol: TCP 12 | selector: 13 | app: my-city-weather-app 14 | -------------------------------------------------------------------------------- /chapter08/deployment/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - base -------------------------------------------------------------------------------- /chapter08/iac/aws/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~>5" 6 | } 7 | } 8 | } 9 | 10 | provider "aws" { 11 | region = "eu-central-1" 12 | 13 | } 14 | 15 | 16 | # ECR creation 17 | resource "aws_ecr_repository" "ecr" { 18 | name = "weather-app" 19 | image_tag_mutability = "MUTABLE" 20 | } 21 | 22 | # VPC and subnets 23 | 24 | module "vpc" { 25 | source = "terraform-aws-modules/vpc/aws" 26 | version = "5.7.0" 27 | 28 | name = "eks-cluster-vpc" 29 | cidr = "10.0.0.0/16" 30 | 31 | # At least two AZs in the region are required for EKS. 32 | azs = ["eu-central-1a", "eu-central-1b"] 33 | private_subnets = ["10.0.0.0/19", "10.0.32.0/19"] 34 | public_subnets = ["10.0.64.0/19", "10.0.96.0/19"] 35 | 36 | enable_nat_gateway = true 37 | single_nat_gateway = true 38 | 39 | enable_dns_hostnames = true 40 | 41 | 42 | public_subnet_tags = { 43 | "kubernetes.io/cluster/eksgitopscluster" = "shared" 44 | "kubernetes.io/role/elb" = 1 45 | } 46 | 47 | private_subnet_tags = { 48 | "kubernetes.io/cluster/eksgitopscluster" = "shared" 49 | "kubernetes.io/role/internal-elb" = 1 50 | } 51 | } 52 | 53 | module "eks" { 54 | source = "terraform-aws-modules/eks/aws" 55 | version = "20.8.4" 56 | 57 | cluster_name = "eksgitopscluster" 58 | cluster_version = "1.29" 59 | 60 | cluster_endpoint_public_access = true 61 | 62 | vpc_id = module.vpc.vpc_id 63 | subnet_ids = module.vpc.private_subnets 64 | 65 | eks_managed_node_group_defaults = { 66 | instance_types = ["t3.small"] 67 | } 68 | 69 | eks_managed_node_groups = { 70 | one = { 71 | name = "node-group-1" 72 | 73 | instance_types = ["t3.small"] 74 | 75 | min_size = 1 76 | max_size = 2 77 | desired_size = 1 78 | } 79 | } 80 | 81 | create_node_security_group = true 82 | } 83 | 84 | 85 | # IAM role to allow EKS nodes to pull images from ECR 86 | resource "aws_iam_policy" "ecr_pull" { 87 | name = "ecr-pull-policy" 88 | path = "/" 89 | description = "IAM policy for EKS to pull images from ECR" 90 | policy = jsonencode({ 91 | Version = "2012-10-17" 92 | Statement = [ 93 | { 94 | Action = "ecr:GetAuthorizationToken", 95 | Effect = "Allow", 96 | Resource = "*" 97 | }, 98 | { 99 | Action = [ 100 | "ecr:BatchCheckLayerAvailability", 101 | "ecr:GetDownloadUrlForLayer", 102 | "ecr:GetRepositoryPolicy", 103 | "ecr:DescribeRepositories", 104 | "ecr:ListImages", 105 | "ecr:DescribeImages", 106 | "ecr:BatchGetImage", 107 | ], 108 | Effect = "Allow", 109 | Resource = aws_ecr_repository.ecr.arn 110 | }, 111 | ] 112 | }) 113 | } 114 | 115 | resource "aws_iam_role_policy_attachment" "ecr_pull_attach" { 116 | role = module.eks.cluster_iam_role_name 117 | policy_arn = aws_iam_policy.ecr_pull.arn 118 | } -------------------------------------------------------------------------------- /chapter08/iac/azure/main.tf: -------------------------------------------------------------------------------- 1 | # Create a Resource Group 2 | resource "azurerm_resource_group" "aks" { 3 | name = "aks-k8s-deployments-rg" 4 | location = "switzerlandnorth" 5 | } 6 | 7 | # Create Azure Container Registry 8 | resource "azurerm_container_registry" "acr" { 9 | name = "aksgitops3003204acr" 10 | resource_group_name = azurerm_resource_group.aks.name 11 | location = azurerm_resource_group.aks.location 12 | sku = "Basic" 13 | admin_enabled = false 14 | } 15 | 16 | # Create Azure Kubernetes Service 17 | resource "azurerm_kubernetes_cluster" "aks" { 18 | name = "aksgitopscluster" 19 | location = azurerm_resource_group.aks.location 20 | resource_group_name = azurerm_resource_group.aks.name 21 | dns_prefix = "aksgitopscluster" 22 | sku_tier = "Free" 23 | 24 | default_node_pool { 25 | name = "default" 26 | node_count = 1 27 | vm_size = "Standard_B2s" 28 | } 29 | 30 | identity { 31 | type = "SystemAssigned" 32 | } 33 | } 34 | 35 | # Attach ACR to AKS using role assignment 36 | resource "azurerm_role_assignment" "acr_attach" { 37 | scope = azurerm_container_registry.acr.id 38 | role_definition_name = "AcrPull" 39 | principal_id = azurerm_kubernetes_cluster.aks.kubelet_identity[0].object_id 40 | } -------------------------------------------------------------------------------- /chapter08/iac/azure/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | azurerm = { 4 | source = "hashicorp/azurerm" 5 | version = "~>3" 6 | } 7 | } 8 | required_version = ">= 1.0" 9 | } 10 | 11 | provider "azurerm" { 12 | features { 13 | resource_group { 14 | prevent_deletion_if_contains_resources = false 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /chapter08/manifests/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion : apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: myweatherappv1 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: myweatherappv1 10 | template: 11 | metadata: 12 | labels: 13 | app: myweatherappv1 14 | spec: 15 | containers: 16 | - name: myweatherappv1 17 | image: aksgitops3003204acr.azurecr.io/myweatherappv1 18 | ports: 19 | - containerPort: 8080 -------------------------------------------------------------------------------- /chapter08/manifests/service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: myweatherappv1 5 | spec: 6 | type: LoadBalancer 7 | ports: 8 | - port: 8080 9 | selector: 10 | app: myweatherappv1 -------------------------------------------------------------------------------- /chapter08/src/data.csv: -------------------------------------------------------------------------------- 1 | Date,Temperature 2 | 2023-01-01,5 3 | 2023-01-02,6 4 | 2023-01-03,5 5 | 2023-01-04,6 6 | 2023-01-05,5 7 | 2023-01-06,5 8 | 2023-01-07,6 9 | 2023-01-08,6 10 | 2023-01-09,7 11 | 2023-01-10,6 12 | 2023-01-11,7 13 | -------------------------------------------------------------------------------- /chapter08/src/dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Node.js runtime as a parent image 2 | FROM node:14 3 | 4 | # Set the working directory 5 | WORKDIR /usr/src/app 6 | 7 | # Copy package.json and install dependencies 8 | COPY package*.json ./ 9 | RUN npm install 10 | 11 | # Bundle app source 12 | COPY . . 13 | 14 | # Expose the port the app runs on 15 | EXPOSE 8080 16 | 17 | # Define the command to run the app 18 | CMD ["node", "server.js"] 19 | -------------------------------------------------------------------------------- /chapter08/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Temperature Chart 5 | 6 | 7 | 8 | 9 | 10 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /chapter08/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "temperature-chart-app", 3 | "version": "1.0.0", 4 | "description": "A simple web app displaying temperature charts from CSV data", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "dependencies": { 10 | "express": "^4.17.1" 11 | }, 12 | "author": "", 13 | "license": "ISC" 14 | } 15 | -------------------------------------------------------------------------------- /chapter08/src/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const port = 8080; 4 | 5 | app.use(express.static('.')); // Serve static files from the current directory 6 | 7 | app.listen(port, () => { 8 | console.log(`Server running at http://localhost:${port}`); 9 | }); 10 | -------------------------------------------------------------------------------- /chapter09/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - main 3 | 4 | resources: 5 | - repo: self 6 | 7 | variables: 8 | 9 | # Container registry service connection established during pipeline creation 10 | dockerRegistryServiceConnection: 'dd42c99b-7b18-42ae-9fe8-fa42ecd0bc04' 11 | imageRepository: 'myweatherappv1' 12 | containerRegistry: 'aksgitops3003204acr.azurecr.io' 13 | dockerfilePath: '**/src/dockerfile' 14 | tag: '$(Build.BuildId)' 15 | imagePullSecret: 'aksgitops3003204acra253-auth' 16 | 17 | # Agent VM image name 18 | vmImageName: 'ubuntu-latest' 19 | 20 | 21 | stages: 22 | - stage: Build 23 | displayName: Build stage 24 | jobs: 25 | - job: Build 26 | displayName: Build 27 | pool: 28 | vmImage: $(vmImageName) 29 | steps: 30 | - task: Docker@2 31 | displayName: Build and push an image to container registry 32 | inputs: 33 | command: buildAndPush 34 | repository: $(imageRepository) 35 | dockerfile: $(dockerfilePath) 36 | containerRegistry: $(dockerRegistryServiceConnection) 37 | tags: | 38 | $(tag) 39 | 40 | - upload: manifests 41 | artifact: manifests 42 | 43 | - stage: Deploy 44 | displayName: Deploy stage 45 | dependsOn: Build 46 | 47 | jobs: 48 | - deployment: Deploy 49 | displayName: Deploy 50 | pool: 51 | vmImage: $(vmImageName) 52 | environment: 'Development' 53 | strategy: 54 | runOnce: 55 | deploy: 56 | steps: 57 | - task: KubernetesManifest@0 58 | displayName: Create imagePullSecret 59 | inputs: 60 | action: createSecret 61 | secretName: $(imagePullSecret) 62 | dockerRegistryEndpoint: $(dockerRegistryServiceConnection) 63 | 64 | - task: KubernetesManifest@0 65 | displayName: Deploy to Kubernetes cluster 66 | inputs: 67 | action: deploy 68 | manifests: | 69 | $(Pipeline.Workspace)/manifests/deployment.yml 70 | $(Pipeline.Workspace)/manifests/service.yml 71 | imagePullSecrets: | 72 | $(imagePullSecret) 73 | containers: | 74 | $(containerRegistry)/$(imageRepository):$(tag) 75 | 76 | -------------------------------------------------------------------------------- /chapter09/deployment/base/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: my-city-weather-app 5 | # namespace: gitopsk8sdeployments 6 | labels: 7 | app: my-city-weather-app 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: my-city-weather-app 13 | template: 14 | metadata: 15 | labels: 16 | app: my-city-weather-app 17 | spec: 18 | containers: 19 | - name: my-city-weather-app 20 | image: 637423230075.dkr.ecr.eu-central-1.amazonaws.com/weather-app:latest 21 | ports: 22 | - containerPort: 8080 -------------------------------------------------------------------------------- /chapter09/deployment/base/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: my-city-weather-app-service 5 | # namespace: gitopsk8sdeployments 6 | spec: 7 | type: LoadBalancer 8 | ports: 9 | - port: 80 10 | targetPort: 8080 11 | protocol: TCP 12 | selector: 13 | app: my-city-weather-app 14 | -------------------------------------------------------------------------------- /chapter09/deployment/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - base -------------------------------------------------------------------------------- /chapter09/iac/aws/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~>5" 6 | } 7 | } 8 | } 9 | 10 | provider "aws" { 11 | region = "eu-central-1" 12 | 13 | } 14 | 15 | 16 | # ECR creation 17 | resource "aws_ecr_repository" "ecr" { 18 | name = "weather-app" 19 | image_tag_mutability = "MUTABLE" 20 | } 21 | 22 | # VPC and subnets 23 | 24 | module "vpc" { 25 | source = "terraform-aws-modules/vpc/aws" 26 | version = "5.7.0" 27 | 28 | name = "eks-cluster-vpc" 29 | cidr = "10.0.0.0/16" 30 | 31 | # At least two AZs in the region are required for EKS. 32 | azs = ["eu-central-1a", "eu-central-1b"] 33 | private_subnets = ["10.0.0.0/19", "10.0.32.0/19"] 34 | public_subnets = ["10.0.64.0/19", "10.0.96.0/19"] 35 | 36 | enable_nat_gateway = true 37 | single_nat_gateway = true 38 | 39 | enable_dns_hostnames = true 40 | 41 | 42 | public_subnet_tags = { 43 | "kubernetes.io/cluster/eksgitopscluster" = "shared" 44 | "kubernetes.io/role/elb" = 1 45 | } 46 | 47 | private_subnet_tags = { 48 | "kubernetes.io/cluster/eksgitopscluster" = "shared" 49 | "kubernetes.io/role/internal-elb" = 1 50 | } 51 | } 52 | 53 | module "eks" { 54 | source = "terraform-aws-modules/eks/aws" 55 | version = "20.8.4" 56 | 57 | cluster_name = "eksgitopscluster" 58 | cluster_version = "1.29" 59 | 60 | cluster_endpoint_public_access = true 61 | 62 | vpc_id = module.vpc.vpc_id 63 | subnet_ids = module.vpc.private_subnets 64 | 65 | eks_managed_node_group_defaults = { 66 | instance_types = ["t3.small"] 67 | } 68 | 69 | eks_managed_node_groups = { 70 | one = { 71 | name = "node-group-1" 72 | 73 | instance_types = ["t3.small"] 74 | 75 | min_size = 1 76 | max_size = 2 77 | desired_size = 1 78 | } 79 | } 80 | 81 | create_node_security_group = true 82 | } 83 | 84 | 85 | # IAM role to allow EKS nodes to pull images from ECR 86 | resource "aws_iam_policy" "ecr_pull" { 87 | name = "ecr-pull-policy" 88 | path = "/" 89 | description = "IAM policy for EKS to pull images from ECR" 90 | policy = jsonencode({ 91 | Version = "2012-10-17" 92 | Statement = [ 93 | { 94 | Action = "ecr:GetAuthorizationToken", 95 | Effect = "Allow", 96 | Resource = "*" 97 | }, 98 | { 99 | Action = [ 100 | "ecr:BatchCheckLayerAvailability", 101 | "ecr:GetDownloadUrlForLayer", 102 | "ecr:GetRepositoryPolicy", 103 | "ecr:DescribeRepositories", 104 | "ecr:ListImages", 105 | "ecr:DescribeImages", 106 | "ecr:BatchGetImage", 107 | ], 108 | Effect = "Allow", 109 | Resource = aws_ecr_repository.ecr.arn 110 | }, 111 | ] 112 | }) 113 | } 114 | 115 | resource "aws_iam_role_policy_attachment" "ecr_pull_attach" { 116 | role = module.eks.cluster_iam_role_name 117 | policy_arn = aws_iam_policy.ecr_pull.arn 118 | } -------------------------------------------------------------------------------- /chapter09/iac/azure/main.tf: -------------------------------------------------------------------------------- 1 | # Create a Resource Group 2 | resource "azurerm_resource_group" "aks" { 3 | name = "aks-k8s-deployments-rg" 4 | location = "switzerlandnorth" 5 | } 6 | 7 | # Create Azure Container Registry 8 | resource "azurerm_container_registry" "acr" { 9 | name = "aksgitops3003204acr" 10 | resource_group_name = azurerm_resource_group.aks.name 11 | location = azurerm_resource_group.aks.location 12 | sku = "Basic" 13 | admin_enabled = false 14 | } 15 | 16 | # Create Azure Kubernetes Service 17 | resource "azurerm_kubernetes_cluster" "aks" { 18 | name = "aksgitopscluster" 19 | location = azurerm_resource_group.aks.location 20 | resource_group_name = azurerm_resource_group.aks.name 21 | dns_prefix = "aksgitopscluster" 22 | sku_tier = "Free" 23 | 24 | default_node_pool { 25 | name = "default" 26 | node_count = 1 27 | vm_size = "Standard_B2s" 28 | } 29 | 30 | identity { 31 | type = "SystemAssigned" 32 | } 33 | } 34 | 35 | # Attach ACR to AKS using role assignment 36 | resource "azurerm_role_assignment" "acr_attach" { 37 | scope = azurerm_container_registry.acr.id 38 | role_definition_name = "AcrPull" 39 | principal_id = azurerm_kubernetes_cluster.aks.kubelet_identity[0].object_id 40 | } -------------------------------------------------------------------------------- /chapter09/iac/azure/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | azurerm = { 4 | source = "hashicorp/azurerm" 5 | version = "~>3" 6 | } 7 | } 8 | required_version = ">= 1.0" 9 | } 10 | 11 | provider "azurerm" { 12 | features { 13 | resource_group { 14 | prevent_deletion_if_contains_resources = false 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /chapter09/manifests/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion : apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: myweatherappv1 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: myweatherappv1 10 | template: 11 | metadata: 12 | labels: 13 | app: myweatherappv1 14 | spec: 15 | containers: 16 | - name: myweatherappv1 17 | image: aksgitops3003204acr.azurecr.io/myweatherappv1 18 | ports: 19 | - containerPort: 8080 -------------------------------------------------------------------------------- /chapter09/manifests/service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: myweatherappv1 5 | spec: 6 | type: LoadBalancer 7 | ports: 8 | - port: 8080 9 | selector: 10 | app: myweatherappv1 -------------------------------------------------------------------------------- /chapter09/src/data.csv: -------------------------------------------------------------------------------- 1 | Date,Temperature 2 | 2023-01-01,5 3 | 2023-01-02,6 4 | 2023-01-03,5 5 | 2023-01-04,6 6 | 2023-01-05,5 7 | 2023-01-06,5 8 | 2023-01-07,6 9 | 2023-01-08,6 10 | 2023-01-09,7 11 | 2023-01-10,6 12 | 2023-01-11,7 13 | -------------------------------------------------------------------------------- /chapter09/src/dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Node.js runtime as a parent image 2 | FROM node:14 3 | 4 | # Set the working directory 5 | WORKDIR /usr/src/app 6 | 7 | # Copy package.json and install dependencies 8 | COPY package*.json ./ 9 | RUN npm install 10 | 11 | # Bundle app source 12 | COPY . . 13 | 14 | # Expose the port the app runs on 15 | EXPOSE 8080 16 | 17 | # Define the command to run the app 18 | CMD ["node", "server.js"] 19 | -------------------------------------------------------------------------------- /chapter09/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Temperature Chart 5 | 6 | 7 | 8 | 9 | 10 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /chapter09/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "temperature-chart-app", 3 | "version": "1.0.0", 4 | "description": "A simple web app displaying temperature charts from CSV data", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "dependencies": { 10 | "express": "^4.17.1" 11 | }, 12 | "author": "", 13 | "license": "ISC" 14 | } 15 | -------------------------------------------------------------------------------- /chapter09/src/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const port = 8080; 4 | 5 | app.use(express.static('.')); // Serve static files from the current directory 6 | 7 | app.listen(port, () => { 8 | console.log(`Server running at http://localhost:${port}`); 9 | }); 10 | -------------------------------------------------------------------------------- /chapter10/Docker/dockerfile: -------------------------------------------------------------------------------- 1 | # Last release of the tf-runner image 2 | # https://github.com/flux-iac/tf-runner-images/pkgs/container/tf-runner 3 | FROM ghcr.io/flux-iac/tf-runner:main-00031bcc@sha256:0a58ffb95144fdf738a4c925b4accfc312c4953b3c4dbf02677001ceb8453b88 4 | 5 | ARG TARGETARCH=amd64 6 | ARG TF_VERSION=1.8.1 7 | 8 | # Switch to root to have permissions for operations 9 | USER root 10 | 11 | ADD https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_${TARGETARCH}.zip /terraform_${TF_VERSION}_linux_${TARGETARCH}.zip 12 | 13 | # Overwrite the existing terraform binary 14 | RUN unzip -o -q /terraform_${TF_VERSION}_linux_${TARGETARCH}.zip -d /usr/local/bin/ && \ 15 | rm /terraform_${TF_VERSION}_linux_${TARGETARCH}.zip && \ 16 | chmod +x /usr/local/bin/terraform 17 | 18 | # Switch back to the non-root user after operations 19 | USER 65532:65532 20 | -------------------------------------------------------------------------------- /chapter10/README.md: -------------------------------------------------------------------------------- 1 | # gitops-terraform-workflow -------------------------------------------------------------------------------- /chapter10/flux-gitops-definitions/dev-iac-automation.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: infra.contrib.fluxcd.io/v1alpha2 2 | kind: Terraform 3 | metadata: 4 | name: dev-cluster-tf-automation 5 | namespace: flux-system 6 | spec: 7 | interval: 1m 8 | approvePlan: auto 9 | destroyResourcesOnDeletion: true 10 | path: ./multi-env/iac/azure/dev 11 | sourceRef: 12 | kind: GitRepository 13 | name: flux-system 14 | runnerPodTemplate: 15 | spec: 16 | env: 17 | - name: ARM_SUBSCRIPTION_ID 18 | valueFrom: 19 | secretKeyRef: 20 | name: azure-creds 21 | key: ARM_SUBSCRIPTION_ID 22 | - name: ARM_CLIENT_ID 23 | valueFrom: 24 | secretKeyRef: 25 | name: azure-creds 26 | key: ARM_CLIENT_ID 27 | - name: ARM_CLIENT_SECRET 28 | valueFrom: 29 | secretKeyRef: 30 | name: azure-creds 31 | key: ARM_CLIENT_SECRET 32 | - name: ARM_TENANT_ID 33 | valueFrom: 34 | secretKeyRef: 35 | name: azure-creds 36 | key: ARM_TENANT_ID -------------------------------------------------------------------------------- /chapter10/flux-gitops-definitions/github-repository-definition.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1 2 | kind: GitRepository 3 | metadata: 4 | name: gitops-terraform-repository 5 | namespace: flux-system 6 | spec: 7 | interval: 1m # Sync interval 8 | url: "https://github.com/[YOUR_REPOSITORY_GOES_HERE]" 9 | secretRef: 10 | name: github-repository-secret 11 | ref: 12 | branch: develop # Specify the branch to track 13 | -------------------------------------------------------------------------------- /chapter10/flux-gitops-definitions/github-repository-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: github-repository-secret 5 | namespace: flux-system 6 | type: Opaque 7 | data: 8 | username: [YOUR_ENCODED_USERNAME] 9 | password: [YOUR_ENCODED_PASSWORD] 10 | -------------------------------------------------------------------------------- /chapter10/flux-gitops-definitions/prod-iac-automation.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: infra.contrib.fluxcd.io/v1alpha2 2 | kind: Terraform 3 | metadata: 4 | name: prod-cluster-tf-automation 5 | namespace: flux-system 6 | spec: 7 | interval: 1m 8 | approvePlan: auto 9 | destroyResourcesOnDeletion: true 10 | path: ./multi-env/iac/azure/prod 11 | sourceRef: 12 | kind: GitRepository 13 | name: flux-system 14 | runnerPodTemplate: 15 | spec: 16 | env: 17 | - name: ARM_SUBSCRIPTION_ID 18 | valueFrom: 19 | secretKeyRef: 20 | name: azure-creds 21 | key: ARM_SUBSCRIPTION_ID 22 | - name: ARM_CLIENT_ID 23 | valueFrom: 24 | secretKeyRef: 25 | name: azure-creds 26 | key: ARM_CLIENT_ID 27 | - name: ARM_CLIENT_SECRET 28 | valueFrom: 29 | secretKeyRef: 30 | name: azure-creds 31 | key: ARM_CLIENT_SECRET 32 | - name: ARM_TENANT_ID 33 | valueFrom: 34 | secretKeyRef: 35 | name: azure-creds 36 | key: ARM_TENANT_ID -------------------------------------------------------------------------------- /chapter10/flux-gitops-definitions/staging-iac-automation.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: infra.contrib.fluxcd.io/v1alpha2 2 | kind: Terraform 3 | metadata: 4 | name: staging-cluster-tf-automation 5 | namespace: flux-system 6 | spec: 7 | interval: 1m 8 | approvePlan: auto 9 | destroyResourcesOnDeletion: true 10 | path: ./multi-env/iac/azure/staging 11 | sourceRef: 12 | kind: GitRepository 13 | name: flux-system 14 | runnerPodTemplate: 15 | spec: 16 | env: 17 | - name: ARM_SUBSCRIPTION_ID 18 | valueFrom: 19 | secretKeyRef: 20 | name: azure-creds 21 | key: ARM_SUBSCRIPTION_ID 22 | - name: ARM_CLIENT_ID 23 | valueFrom: 24 | secretKeyRef: 25 | name: azure-creds 26 | key: ARM_CLIENT_ID 27 | - name: ARM_CLIENT_SECRET 28 | valueFrom: 29 | secretKeyRef: 30 | name: azure-creds 31 | key: ARM_CLIENT_SECRET 32 | - name: ARM_TENANT_ID 33 | valueFrom: 34 | secretKeyRef: 35 | name: azure-creds 36 | key: ARM_TENANT_ID -------------------------------------------------------------------------------- /chapter10/iac/azure/vnet/main.tf: -------------------------------------------------------------------------------- 1 | provider "azurerm" { 2 | features {} 3 | } 4 | 5 | 6 | resource "azurerm_resource_group" "gitops_terraform_rg" { 7 | name = "gitops-terraform-rg" 8 | location = "switzerlandnorth" 9 | } 10 | 11 | resource "azurerm_virtual_network" "gitops_terraform_vnet" { 12 | name = "gitops-terraform-vnet" 13 | resource_group_name = azurerm_resource_group.gitops_terraform_rg.name 14 | location = azurerm_resource_group.gitops_terraform_rg.location 15 | address_space = ["10.0.0.0/16"] 16 | } 17 | 18 | resource "azurerm_subnet" "default_subnet" { 19 | name = "default" 20 | resource_group_name = azurerm_resource_group.gitops_terraform_rg.name 21 | virtual_network_name = azurerm_virtual_network.gitops_terraform_vnet.name 22 | address_prefixes = ["10.0.0.0/24"] 23 | } 24 | 25 | resource "azurerm_subnet" "azure_bastion_subnet" { 26 | name = "AzureBastionSubnet" 27 | resource_group_name = azurerm_resource_group.gitops_terraform_rg.name 28 | virtual_network_name = azurerm_virtual_network.gitops_terraform_vnet.name 29 | address_prefixes = ["10.0.2.0/26"] 30 | } -------------------------------------------------------------------------------- /chapter10/multi-env/iac/azure/base/main.tf: -------------------------------------------------------------------------------- 1 | 2 | provider "azurerm" { 3 | features {} 4 | } 5 | 6 | 7 | resource "azurerm_resource_group" "gitops_rg" { 8 | name = var.rg 9 | location = var.location 10 | } 11 | 12 | resource "azurerm_virtual_network" "gitops_vnet" { 13 | name = "gitops-${var.environment}-vnet" 14 | resource_group_name = azurerm_resource_group.gitops_rg.name 15 | location = azurerm_resource_group.gitops_rg.location 16 | address_space = ["10.0.0.0/16"] 17 | } 18 | 19 | resource "azurerm_subnet" "default_subnet" { 20 | name = "default" 21 | resource_group_name = azurerm_resource_group.gitops_rg.name 22 | virtual_network_name = azurerm_virtual_network.gitops_vnet.name 23 | address_prefixes = ["10.0.0.0/24"] 24 | } 25 | 26 | resource "azurerm_subnet" "aks_subnet" { 27 | name = "gitops-${var.environment}-aks-subnet" 28 | resource_group_name = azurerm_resource_group.gitops_rg.name 29 | virtual_network_name = azurerm_virtual_network.gitops_vnet.name 30 | address_prefixes = ["10.0.1.0/24"] 31 | } 32 | 33 | resource "azurerm_kubernetes_cluster" "gitops_aks" { 34 | name = "gitops-${var.environment}-aks" 35 | location = azurerm_resource_group.gitops_rg.location 36 | resource_group_name = azurerm_resource_group.gitops_rg.name 37 | dns_prefix = "gitops-${var.environment}-aks" 38 | kubernetes_version = "1.28.5" 39 | private_cluster_enabled = false 40 | 41 | default_node_pool { 42 | name = "default" 43 | node_count = 1 44 | vm_size = "Standard_B2s" 45 | os_disk_size_gb = 30 46 | type = "VirtualMachineScaleSets" 47 | } 48 | 49 | identity { 50 | type = "SystemAssigned" 51 | } 52 | 53 | network_profile { 54 | network_plugin = "azure" 55 | } 56 | 57 | } 58 | 59 | # Define an Azure Container Registry with the name gitops-terraform-acr in the same resource group as the AKS cluster. 60 | # The SKU is set to Basic and the admin user is disabled. 61 | 62 | resource "azurerm_container_registry" "gitops_acr" { 63 | name = "gitops${var.environment}acr" 64 | resource_group_name = azurerm_resource_group.gitops_rg.name 65 | location = azurerm_resource_group.gitops_rg.location 66 | sku = "Standard" 67 | admin_enabled = false 68 | } 69 | 70 | # Define a role assignment to allow the AKS cluster to pull images from the ACR. 71 | 72 | resource "azurerm_role_assignment" "gitops_acr_role" { 73 | scope = azurerm_container_registry.gitops_acr.id 74 | role_definition_name = "AcrPull" 75 | principal_id = azurerm_kubernetes_cluster.gitops_aks.kubelet_identity[0].object_id 76 | } -------------------------------------------------------------------------------- /chapter10/multi-env/iac/azure/base/readme.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | This Terraform file is used to create resources in Azure. 4 | 5 | ### Provider 6 | 7 | The `azurerm` provider is used to interact with the many resources supported by Azure Resource Manager (AzureRM) through its APIs. 8 | 9 | ``` 10 | provider "azurerm" { 11 | features {} 12 | } 13 | ``` 14 | 15 | ## Resources 16 | 17 | ### Resource Group 18 | 19 | The azurerm_resource_group resource creates a new resource group in Azure. The name and location of the resource group are defined by the variables var.rg and var.location respectively. 20 | 21 | ``` 22 | resource "azurerm_resource_group" "gitops_rg" { 23 | name = var.rg 24 | location = var.location 25 | } 26 | ``` 27 | 28 | ### Virtual Network 29 | 30 | The azurerm_virtual_network resource creates a new virtual network in Azure. The name of the virtual network is a combination of the string "gitops-" and the value of var.environment, followed by "-vnet". The resource group name and location are derived from the previously created resource group. The address space for the virtual network is defined as "10.0.0.0/16". 31 | 32 | ``` 33 | resource "azurerm_virtual_network" "gitops_vnet" { 34 | name = "gitops-${var.environment}-vnet" 35 | resource_group_name = azurerm_resource_group.gitops_rg.name 36 | location = azurerm_resource_group.gitops_rg.location 37 | address_space = ["10.0.0.0/16"] 38 | } 39 | ``` 40 | 41 | ### Subnets 42 | Two subnets are created within the virtual network. 43 | 44 | ### Default Subnet 45 | The first subnet, named "default", uses the address prefix "10.0.0.0/24". 46 | 47 | ``` 48 | resource "azurerm_subnet" "default_subnet" { 49 | name = "default" 50 | resource_group_name = azurerm_resource_group.gitops_rg.name 51 | virtual_network_name = azurerm_virtual_network.gitops_vnet.name 52 | address_prefixes = ["10.0.0.0/24"] 53 | } 54 | ``` 55 | 56 | ### AKS Subnet 57 | 58 | The second subnet, named "aks_subnet", is not fully defined in the provided excerpt. 59 | 60 | ``` 61 | resource "azurerm_subnet" "aks_subnet" { 62 | name = "gitops-${var.environment}-aks-subnet" 63 | resource_group_name = azurerm_resource_group.gitops_rg.name 64 | virtual_network_name = azurerm_virtual_network.gitops_vnet.name 65 | address_prefixes = ["10.0.1.0/24"] 66 | } 67 | ``` 68 | -------------------------------------------------------------------------------- /chapter10/multi-env/iac/azure/base/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | description = "The environment in which the resources will be created." 3 | type = string 4 | default = "dev" 5 | } 6 | 7 | variable "location" { 8 | description = "The location in which the resources will be created." 9 | type = string 10 | default = "switzerlandnorth" 11 | 12 | } 13 | 14 | variable "rg" { 15 | description = "The name of the resource group in which to create the resources." 16 | type = string 17 | default = "gitops-dev-rg" 18 | } 19 | -------------------------------------------------------------------------------- /chapter10/multi-env/iac/azure/dev/main.tf: -------------------------------------------------------------------------------- 1 | 2 | provider "azurerm" { 3 | features {} 4 | } 5 | 6 | 7 | resource "azurerm_resource_group" "gitops_rg" { 8 | name = var.rg 9 | location = var.location 10 | } 11 | 12 | resource "azurerm_virtual_network" "gitops_vnet" { 13 | name = "gitops-${var.environment}-vnet" 14 | resource_group_name = azurerm_resource_group.gitops_rg.name 15 | location = azurerm_resource_group.gitops_rg.location 16 | address_space = ["10.0.0.0/16"] 17 | } 18 | 19 | resource "azurerm_subnet" "default_subnet" { 20 | name = "default" 21 | resource_group_name = azurerm_resource_group.gitops_rg.name 22 | virtual_network_name = azurerm_virtual_network.gitops_vnet.name 23 | address_prefixes = ["10.0.0.0/24"] 24 | } 25 | 26 | resource "azurerm_subnet" "aks_subnet" { 27 | name = "gitops-${var.environment}-aks-subnet" 28 | resource_group_name = azurerm_resource_group.gitops_rg.name 29 | virtual_network_name = azurerm_virtual_network.gitops_vnet.name 30 | address_prefixes = ["10.0.1.0/24"] 31 | } 32 | 33 | resource "azurerm_kubernetes_cluster" "gitops_aks" { 34 | name = "gitops-${var.environment}-aks" 35 | location = azurerm_resource_group.gitops_rg.location 36 | resource_group_name = azurerm_resource_group.gitops_rg.name 37 | dns_prefix = "gitops-${var.environment}-aks" 38 | kubernetes_version = "1.28.5" 39 | private_cluster_enabled = false 40 | 41 | default_node_pool { 42 | name = "default" 43 | node_count = 1 44 | vm_size = "Standard_B2s" 45 | os_disk_size_gb = 30 46 | type = "VirtualMachineScaleSets" 47 | } 48 | 49 | identity { 50 | type = "SystemAssigned" 51 | } 52 | 53 | network_profile { 54 | network_plugin = "azure" 55 | } 56 | 57 | } 58 | 59 | # Define an Azure Container Registry with the name gitops-terraform-acr in the same resource group as the AKS cluster. 60 | # The SKU is set to Basic and the admin user is disabled. 61 | 62 | resource "azurerm_container_registry" "gitops_acr" { 63 | name = "gitops${var.environment}acr" 64 | resource_group_name = azurerm_resource_group.gitops_rg.name 65 | location = azurerm_resource_group.gitops_rg.location 66 | sku = "Standard" 67 | admin_enabled = false 68 | } 69 | 70 | # Define a role assignment to allow the AKS cluster to pull images from the ACR. 71 | 72 | resource "azurerm_role_assignment" "gitops_acr_role" { 73 | scope = azurerm_container_registry.gitops_acr.id 74 | role_definition_name = "AcrPull" 75 | principal_id = azurerm_kubernetes_cluster.gitops_aks.kubelet_identity[0].object_id 76 | } -------------------------------------------------------------------------------- /chapter10/multi-env/iac/azure/dev/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | description = "The environment in which the resources will be created." 3 | type = string 4 | default = "dev" 5 | } 6 | 7 | variable "location" { 8 | description = "The location in which the resources will be created." 9 | type = string 10 | default = "switzerlandnorth" 11 | 12 | } 13 | 14 | variable "rg" { 15 | description = "The name of the resource group in which to create the resources." 16 | type = string 17 | default = "gitops-dev-rg" 18 | } 19 | -------------------------------------------------------------------------------- /chapter10/multi-env/iac/azure/prod/main.tf: -------------------------------------------------------------------------------- 1 | 2 | provider "azurerm" { 3 | features {} 4 | } 5 | 6 | 7 | resource "azurerm_resource_group" "gitops_rg" { 8 | name = var.rg 9 | location = var.location 10 | } 11 | 12 | resource "azurerm_virtual_network" "gitops_vnet" { 13 | name = "gitops-${var.environment}-vnet" 14 | resource_group_name = azurerm_resource_group.gitops_rg.name 15 | location = azurerm_resource_group.gitops_rg.location 16 | address_space = ["10.0.0.0/16"] 17 | } 18 | 19 | resource "azurerm_subnet" "default_subnet" { 20 | name = "default" 21 | resource_group_name = azurerm_resource_group.gitops_rg.name 22 | virtual_network_name = azurerm_virtual_network.gitops_vnet.name 23 | address_prefixes = ["10.0.0.0/24"] 24 | } 25 | 26 | resource "azurerm_subnet" "aks_subnet" { 27 | name = "gitops-${var.environment}-aks-subnet" 28 | resource_group_name = azurerm_resource_group.gitops_rg.name 29 | virtual_network_name = azurerm_virtual_network.gitops_vnet.name 30 | address_prefixes = ["10.0.1.0/24"] 31 | } 32 | 33 | resource "azurerm_kubernetes_cluster" "gitops_aks" { 34 | name = "gitops-${var.environment}-aks" 35 | location = azurerm_resource_group.gitops_rg.location 36 | resource_group_name = azurerm_resource_group.gitops_rg.name 37 | dns_prefix = "gitops-${var.environment}-aks" 38 | kubernetes_version = "1.28.5" 39 | private_cluster_enabled = false 40 | 41 | default_node_pool { 42 | name = "default" 43 | node_count = 1 44 | vm_size = "Standard_B2s" 45 | os_disk_size_gb = 30 46 | type = "VirtualMachineScaleSets" 47 | } 48 | 49 | identity { 50 | type = "SystemAssigned" 51 | } 52 | 53 | network_profile { 54 | network_plugin = "azure" 55 | } 56 | 57 | } 58 | 59 | # Define an Azure Container Registry with the name gitops-terraform-acr in the same resource group as the AKS cluster. 60 | # The SKU is set to Basic and the admin user is disabled. 61 | 62 | resource "azurerm_container_registry" "gitops_acr" { 63 | name = "gitops${var.environment}acr" 64 | resource_group_name = azurerm_resource_group.gitops_rg.name 65 | location = azurerm_resource_group.gitops_rg.location 66 | sku = "Standard" 67 | admin_enabled = false 68 | } 69 | 70 | # Define a role assignment to allow the AKS cluster to pull images from the ACR. 71 | 72 | resource "azurerm_role_assignment" "gitops_acr_role" { 73 | scope = azurerm_container_registry.gitops_acr.id 74 | role_definition_name = "AcrPull" 75 | principal_id = azurerm_kubernetes_cluster.gitops_aks.kubelet_identity[0].object_id 76 | } -------------------------------------------------------------------------------- /chapter10/multi-env/iac/azure/prod/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | description = "The environment in which the resources will be created." 3 | type = string 4 | default = "prod" 5 | } 6 | 7 | variable "location" { 8 | description = "The location in which the resources will be created." 9 | type = string 10 | default = "switzerlandnorth" 11 | 12 | } 13 | 14 | variable "rg" { 15 | description = "The name of the resource group in which to create the resources." 16 | type = string 17 | default = "gitops-prod-rg" 18 | } 19 | -------------------------------------------------------------------------------- /chapter10/multi-env/iac/azure/staging/main.tf: -------------------------------------------------------------------------------- 1 | 2 | provider "azurerm" { 3 | features {} 4 | } 5 | 6 | 7 | resource "azurerm_resource_group" "gitops_rg" { 8 | name = var.rg 9 | location = var.location 10 | } 11 | 12 | resource "azurerm_virtual_network" "gitops_vnet" { 13 | name = "gitops-${var.environment}-vnet" 14 | resource_group_name = azurerm_resource_group.gitops_rg.name 15 | location = azurerm_resource_group.gitops_rg.location 16 | address_space = ["10.0.0.0/16"] 17 | } 18 | 19 | resource "azurerm_subnet" "default_subnet" { 20 | name = "default" 21 | resource_group_name = azurerm_resource_group.gitops_rg.name 22 | virtual_network_name = azurerm_virtual_network.gitops_vnet.name 23 | address_prefixes = ["10.0.0.0/24"] 24 | } 25 | 26 | resource "azurerm_subnet" "aks_subnet" { 27 | name = "gitops-${var.environment}-aks-subnet" 28 | resource_group_name = azurerm_resource_group.gitops_rg.name 29 | virtual_network_name = azurerm_virtual_network.gitops_vnet.name 30 | address_prefixes = ["10.0.1.0/24"] 31 | } 32 | 33 | resource "azurerm_kubernetes_cluster" "gitops_aks" { 34 | name = "gitops-${var.environment}-aks" 35 | location = azurerm_resource_group.gitops_rg.location 36 | resource_group_name = azurerm_resource_group.gitops_rg.name 37 | dns_prefix = "gitops-${var.environment}-aks" 38 | kubernetes_version = "1.28.5" 39 | private_cluster_enabled = false 40 | 41 | default_node_pool { 42 | name = "default" 43 | node_count = 1 44 | vm_size = "Standard_B2s" 45 | os_disk_size_gb = 30 46 | type = "VirtualMachineScaleSets" 47 | } 48 | 49 | identity { 50 | type = "SystemAssigned" 51 | } 52 | 53 | network_profile { 54 | network_plugin = "azure" 55 | } 56 | 57 | } 58 | 59 | # Define an Azure Container Registry with the name gitops-terraform-acr in the same resource group as the AKS cluster. 60 | # The SKU is set to Basic and the admin user is disabled. 61 | 62 | resource "azurerm_container_registry" "gitops_acr" { 63 | name = "gitops${var.environment}acr" 64 | resource_group_name = azurerm_resource_group.gitops_rg.name 65 | location = azurerm_resource_group.gitops_rg.location 66 | sku = "Standard" 67 | admin_enabled = false 68 | } 69 | 70 | # Define a role assignment to allow the AKS cluster to pull images from the ACR. 71 | 72 | resource "azurerm_role_assignment" "gitops_acr_role" { 73 | scope = azurerm_container_registry.gitops_acr.id 74 | role_definition_name = "AcrPull" 75 | principal_id = azurerm_kubernetes_cluster.gitops_aks.kubelet_identity[0].object_id 76 | } -------------------------------------------------------------------------------- /chapter10/multi-env/iac/azure/staging/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | description = "The environment in which the resources will be created." 3 | type = string 4 | default = "staging" 5 | } 6 | 7 | variable "location" { 8 | description = "The location in which the resources will be created." 9 | type = string 10 | default = "switzerlandnorth" 11 | 12 | } 13 | 14 | variable "rg" { 15 | description = "The name of the resource group in which to create the resources." 16 | type = string 17 | default = "gitops-staging-rg" 18 | } 19 | -------------------------------------------------------------------------------- /chapter11/Step-01/ArgoCD-GitOps/argocd_deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: backend-api-weather-app 5 | namespace: argocd 6 | spec: 7 | destination: 8 | namespace: weather-app-for-real 9 | server: https://kubernetes.default.svc 10 | project: default 11 | source: 12 | repoURL: https://github.com/pietrolibro/gitops-for-real-world 13 | path: ./Step-01/deployment 14 | targetRevision: main 15 | -------------------------------------------------------------------------------- /chapter11/Step-01/backend-api.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, jsonify 2 | import requests 3 | import os 4 | 5 | app = Flask(__name__) 6 | 7 | @app.route('/weather', methods=['GET']) 8 | def get_weather(): 9 | city = request.args.get('city', 'Zurich') # Default to Zurich if no city parameter is provided 10 | api_key = os.getenv('WEATHER_API_KEY') # Get the API key from the environment variables 11 | if not api_key: 12 | return jsonify({'error': 'API key is not configured'}), 500 13 | 14 | url = f"http://api.weatherapi.com/v1/current.json" 15 | params = { 16 | 'key': api_key, 17 | 'q': city, 18 | 'aqi': 'no' # Air quality data is not needed for this basic example 19 | } 20 | response = requests.get(url, params=params) 21 | if response.status_code != 200: 22 | return jsonify({'error': 'Failed to fetch weather data'}), response.status_code 23 | 24 | return jsonify(response.json()) 25 | 26 | if __name__ == '__main__': 27 | app.run(host='0.0.0.0', port=80) 28 | -------------------------------------------------------------------------------- /chapter11/Step-01/deployment/backend-api-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: backend-api-weather-app 5 | namespace: weather-app-for-real 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: backend-api-weather-app 11 | template: 12 | metadata: 13 | labels: 14 | app: backend-api-weather-app 15 | spec: 16 | containers: 17 | - name: weather-app-backend-api 18 | image: pietrolibro/weather-app-backend-api:vcc5fdd842988b0baf15b08c4bb4ad963ca1ebfea 19 | imagePullPolicy: Always # Forces Kubernetes to always pull the image 20 | ports: 21 | - containerPort: 80 22 | env: 23 | - name: WEATHER_API_KEY # Environment variable your app uses to access the API key 24 | valueFrom: 25 | secretKeyRef: 26 | name: weather-api-key # The name of the Kubernetes Secret 27 | key: WEATHER_API_KEY # The key of the data stored in the Secret 28 | --- 29 | apiVersion: v1 30 | kind: Service 31 | metadata: 32 | name: backend-api-service 33 | namespace: weather-app-for-real 34 | spec: 35 | selector: 36 | app: backend-api-weather-app 37 | ports: 38 | - protocol: TCP 39 | port: 80 40 | type: LoadBalancer # We are using LoadBalancer type to get a public ip address. 41 | -------------------------------------------------------------------------------- /chapter11/Step-01/deployment/backend-api-secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: weather-api-key 5 | namespace: weather-app-for-real 6 | type: Opaque 7 | data: 8 | WEATHER_API_KEY: YOUR_API_TOKEN_GOES_HERE 9 | -------------------------------------------------------------------------------- /chapter11/Step-01/dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Python runtime as a parent image 2 | FROM python:3.9-slim 3 | 4 | # Set the working directory in the container to /app 5 | WORKDIR /app 6 | 7 | # Copy the current directory contents into the container at /app 8 | COPY . /app 9 | 10 | RUN ls -la 11 | 12 | # Install any needed packages specified in requirements.txt 13 | RUN pip install --no-cache-dir -r requirements.txt 14 | 15 | # Make port 80 available to the world outside this container 16 | # EXPOSE 5000 17 | EXPOSE 80 18 | 19 | # Define environment variable to store the API key 20 | ENV WEATHER_API_KEY=NOT_VALID_KEY 21 | 22 | # Run app.py when the container launches 23 | CMD ["python", "backend-api.py"] 24 | -------------------------------------------------------------------------------- /chapter11/Step-01/requirements.txt: -------------------------------------------------------------------------------- 1 | blinker==1.8.1 2 | certifi==2024.2.2 3 | charset-normalizer==3.3.2 4 | click==8.1.7 5 | Flask==3.0.3 6 | idna==3.7 7 | itsdangerous==2.2.0 8 | Jinja2>=3.1.4 9 | MarkupSafe==2.1.5 10 | requests>=2.32.0 11 | urllib3==2.2.1 12 | Werkzeug>=3.0.3 13 | -------------------------------------------------------------------------------- /chapter11/Step-01/scripts/get-argo-cd-external-ip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define the service and namespace 4 | SERVICE_NAME="argocd-server" 5 | NAMESPACE="argocd" 6 | 7 | # Initialize external IP 8 | export EXTERNAL_IP="" 9 | 10 | # Loop until the external IP is assigned 11 | while [ -z $EXTERNAL_IP ]; do 12 | echo "Waiting for external IP..." 13 | EXTERNAL_IP=$(kubectl get svc $SERVICE_NAME -n $NAMESPACE --template="{{range .status.loadBalancer.ingress}}{{.ip}}{{end}}") 14 | # Wait for 10 seconds before checking again 15 | sleep 10 16 | done 17 | 18 | echo "External IP assigned: $EXTERNAL_IP" 19 | -------------------------------------------------------------------------------- /chapter11/Step-01/scripts/get-argo-cd-initial-password.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define the namespace where Argo CD is installed 4 | NAMESPACE="argocd" 5 | 6 | # Define the name of the secret where the initial password is stored 7 | SECRET_NAME="argocd-initial-admin-secret" 8 | 9 | export ARGO_CD_PASSWORD="" 10 | 11 | 12 | # Function to get the Argo CD admin password 13 | function get_argo_cd_password() { 14 | # Loop until the password is retrieved 15 | while true; do 16 | # Extract the password from the secret 17 | ARGO_CD_PASSWORD=$(kubectl get secret $SECRET_NAME -n $NAMESPACE -o jsonpath="{.data.password}" 2>/dev/null | base64 --decode) 18 | 19 | # Check if password is successfully retrieved and non-empty 20 | if [[ ! -z "$ARGO_CD_PASSWORD" ]]; then 21 | echo "Successfully retrieved the initial password for Argo CD." 22 | echo "Initial Admin Password: $ARGO_CD_PASSWORD" 23 | break # Exit the loop if password is retrieved 24 | else 25 | echo "Waiting for the initial password to be set in the secret..." 26 | sleep 10 # Wait for 10 seconds before trying again 27 | fi 28 | done 29 | } 30 | 31 | # Check if the secret exists in the specified namespace 32 | if kubectl get secret $SECRET_NAME -n $NAMESPACE &> /dev/null; then 33 | echo "Secret '$SECRET_NAME' found in namespace '$NAMESPACE'." 34 | get_argo_cd_password 35 | else 36 | echo "Secret '$SECRET_NAME' not found in namespace '$NAMESPACE'." 37 | echo "Ensure Argo CD is installed and the secret name is correct." 38 | exit 1 39 | fi 40 | -------------------------------------------------------------------------------- /chapter11/Step-01/terraform/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | azurerm = { 4 | source = "hashicorp/azurerm" 5 | version = "~> 3.103" 6 | } 7 | } 8 | 9 | backend "azurerm" { 10 | } 11 | } 12 | 13 | provider "azurerm" { 14 | features {} 15 | } 16 | 17 | 18 | resource "azurerm_resource_group" "gitops_rg" { 19 | name = var.rg 20 | location = var.location 21 | } 22 | 23 | resource "azurerm_virtual_network" "gitops_vnet" { 24 | name = "gitops-${var.environment}-vnet" 25 | resource_group_name = azurerm_resource_group.gitops_rg.name 26 | location = azurerm_resource_group.gitops_rg.location 27 | address_space = ["10.0.0.0/16"] 28 | } 29 | 30 | resource "azurerm_subnet" "default_subnet" { 31 | name = "default" 32 | resource_group_name = azurerm_resource_group.gitops_rg.name 33 | virtual_network_name = azurerm_virtual_network.gitops_vnet.name 34 | address_prefixes = ["10.0.0.0/24"] 35 | } 36 | 37 | resource "azurerm_subnet" "aks_subnet" { 38 | name = "gitops-${var.environment}-aks-subnet" 39 | resource_group_name = azurerm_resource_group.gitops_rg.name 40 | virtual_network_name = azurerm_virtual_network.gitops_vnet.name 41 | address_prefixes = ["10.0.1.0/24"] 42 | } 43 | 44 | resource "azurerm_kubernetes_cluster" "gitops_aks" { 45 | name = "gitops-${var.environment}-aks" 46 | location = azurerm_resource_group.gitops_rg.location 47 | resource_group_name = azurerm_resource_group.gitops_rg.name 48 | dns_prefix = "gitops-${var.environment}-aks" 49 | kubernetes_version = "1.28.5" 50 | private_cluster_enabled = false 51 | 52 | default_node_pool { 53 | name = "default" 54 | node_count = 1 55 | vm_size = "Standard_D2_v2" 56 | os_disk_size_gb = 30 57 | type = "VirtualMachineScaleSets" 58 | } 59 | 60 | identity { 61 | type = "SystemAssigned" 62 | } 63 | 64 | network_profile { 65 | network_plugin = "azure" 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /chapter11/Step-01/terraform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | description = "The environment in which the resources will be created." 3 | type = string 4 | default = "real" 5 | } 6 | 7 | variable "location" { 8 | description = "The location in which the resources will be created." 9 | type = string 10 | default = "switzerlandnorth" 11 | 12 | } 13 | 14 | variable "rg" { 15 | description = "The name of the resource group in which to create the resources." 16 | type = string 17 | default = "gitops-real-rg" 18 | } 19 | -------------------------------------------------------------------------------- /chapter11/Step-03-Scalability/deployment/backend-api-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: backend-api-weather-app 5 | namespace: weather-app-for-real 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: backend-api-weather-app 11 | template: 12 | metadata: 13 | labels: 14 | app: backend-api-weather-app 15 | spec: 16 | containers: 17 | - name: weather-app-backend-api 18 | image: pietrolibro/weather-app-backend-api:v2.0 19 | imagePullPolicy: Always # Forces Kubernetes to always pull the image 20 | ports: 21 | - containerPort: 80 22 | env: 23 | - name: WEATHER_API_KEY # Environment variable your app uses to access the API key 24 | valueFrom: 25 | secretKeyRef: 26 | name: weather-api-key # The name of the Kubernetes Secret 27 | key: WEATHER_API_KEY # The key of the data stored in the Secret 28 | resources: 29 | requests: 30 | cpu: "100m" 31 | memory: "100Mi" 32 | limits: 33 | cpu: "150m" 34 | memory: "150Mi" 35 | --- 36 | apiVersion: v1 37 | kind: Service 38 | metadata: 39 | name: backend-api-service 40 | namespace: weather-app-for-real 41 | spec: 42 | selector: 43 | app: backend-api-weather-app 44 | ports: 45 | - protocol: TCP 46 | port: 80 47 | type: LoadBalancer # We are using LoadBalancer type to get a public ip address. 48 | -------------------------------------------------------------------------------- /chapter11/Step-03-Scalability/deployment/hpa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling/v1 2 | kind: HorizontalPodAutoscaler 3 | metadata: 4 | name: weather-app-backend-api-hpa 5 | namespace: weather-app-for-real 6 | spec: 7 | scaleTargetRef: 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | name: backend-api-weather-app 11 | minReplicas: 1 12 | maxReplicas: 5 13 | targetCPUUtilizationPercentage: 5 -------------------------------------------------------------------------------- /chapter11/Step-03-Scalability/hpa-testing.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Base URL of your weather service 4 | baseUrl="http://[PUBLIC_IP]/weather" 5 | 6 | # Array of cities 7 | cities=("New York" "London" "Paris" "Berlin" "Tokyo" "Sydney" "Moscow" "Cairo" "Rio de Janeiro" "Johannesburg") 8 | 9 | # Function to get a random city from the list 10 | function get_random_city { 11 | echo ${cities[$RANDOM % ${#cities[@]}]} 12 | } 13 | 14 | # Infinite loop to continuously send requests 15 | while true; do 16 | # Select a random city from the list 17 | randomCity=$(get_random_city) 18 | 19 | # Generate a random number for the cache buster 20 | cacheBuster=$((RANDOM % 10000)) 21 | 22 | # Construct the full URL with the city and cache buster 23 | url="${baseUrl}?city=${randomCity}&cb=${cacheBuster}" 24 | 25 | # Send the HTTP GET request 26 | response=$(curl --silent --write-out "HTTPSTATUS:%{http_code}" -X GET $url) 27 | 28 | # Extract the body and the status 29 | httpBody=$(echo $response | sed -e 's/HTTPSTATUS\:.*//g') 30 | httpStatus=$(echo $response | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') 31 | 32 | # Optional: Print the status code and response body 33 | echo "Status Code: $httpStatus" 34 | echo "Response Body: $httpBody" 35 | 36 | # Wait for 100 milliseconds before sending the next request. 37 | sleep 0.1 38 | done 39 | -------------------------------------------------------------------------------- /chapter11/Step-04-Security/weather-app-manager-role-binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: weather-app-manager-binding 5 | namespace: weather-app-for-real 6 | subjects: 7 | - kind: User 8 | name: weather-app-manager 9 | apiGroup: rbac.authorization.k8s.io 10 | roleRef: 11 | kind: Role 12 | name: weather-app-manager 13 | apiGroup: rbac.authorization.k8s.io 14 | -------------------------------------------------------------------------------- /chapter11/Step-04-Security/weather-app-manager-role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | namespace: weather-app-for-real 5 | name: weather-app-manager 6 | rules: 7 | - apiGroups: ["", "apps"] 8 | resources: ["deployments", "replicasets", "pods", "services"] 9 | verbs: ["get", "list", "watch", "create", "update", "delete"] 10 | -------------------------------------------------------------------------------- /chapter11/Step-04-Security/weather-app-operator-role-binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: weather-app-operator-binding 5 | namespace: weather-app-for-real 6 | subjects: 7 | - kind: User 8 | name: weather-app-operator 9 | apiGroup: rbac.authorization.k8s.io 10 | roleRef: 11 | kind: Role 12 | name: weather-app-operator 13 | apiGroup: rbac.authorization.k8s.io 14 | -------------------------------------------------------------------------------- /chapter11/Step-04-Security/weather-app-operator-role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | namespace: weather-app-for-real 5 | name: weather-app-operator 6 | rules: 7 | - apiGroups: ["", "apps"] 8 | resources: ["pods", "services"] 9 | verbs: ["get", "list", "watch"] -------------------------------------------------------------------------------- /chapter13/chapter-13-committing-everything-to-git-and-what-about-secrets/applicationsets/security/external-secrets-applicationset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: ApplicationSet 3 | metadata: 4 | name: external-secrets 5 | namespace: argocd 6 | spec: 7 | generators: 8 | - clusters: 9 | selector: 10 | matchLabels: 11 | env: dev 12 | values: 13 | branch: development 14 | - clusters: 15 | selector: 16 | matchLabels: 17 | env: prod 18 | values: 19 | branch: main 20 | template: 21 | metadata: 22 | name: "{{name}}-external-secrets" 23 | annotations: 24 | argocd.argoproj.io/manifest-generate-paths: ".;.." 25 | spec: 26 | project: default 27 | sources: 28 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git 29 | targetRevision: "{{values.branch}}" 30 | path: "./chapter13/chapter-13-committing-everything-to-git-and-what-about-secrets/security/external-secrets" 31 | helm: 32 | releaseName: "external-secrets" # Release name override (defaults to application name) 33 | valueFiles: 34 | - "values.yaml" 35 | destination: 36 | name: "{{name}}" 37 | namespace: "external-secrets" 38 | syncPolicy: 39 | automated: 40 | prune: false 41 | selfHeal: true 42 | syncOptions: 43 | - CreateNamespace=true 44 | retry: 45 | limit: 5 46 | -------------------------------------------------------------------------------- /chapter13/chapter-13-committing-everything-to-git-and-what-about-secrets/applicationsets/security/sealed-secrets-applicationset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: ApplicationSet 3 | metadata: 4 | name: sealed-secrets 5 | namespace: argocd 6 | spec: 7 | generators: 8 | - clusters: 9 | selector: 10 | matchLabels: 11 | env: dev 12 | values: 13 | branch: development 14 | - clusters: 15 | selector: 16 | matchLabels: 17 | env: prod 18 | values: 19 | branch: main 20 | template: 21 | metadata: 22 | name: "{{name}}-sealed-secrets" 23 | annotations: 24 | argocd.argoproj.io/manifest-generate-paths: ".;.." 25 | spec: 26 | project: default 27 | sources: 28 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git 29 | targetRevision: "{{values.branch}}" 30 | path: "./chapter13/chapter-13-committing-everything-to-git-and-what-about-secrets/security/sealed-secrets" 31 | helm: 32 | releaseName: "sealed-secrets" # Release name override (defaults to application name) 33 | valueFiles: 34 | - "values.yaml" 35 | destination: 36 | name: "{{name}}" 37 | namespace: "sealed-secrets" 38 | syncPolicy: 39 | automated: 40 | prune: false 41 | selfHeal: true 42 | syncOptions: 43 | - CreateNamespace=true 44 | retry: 45 | limit: 5 46 | -------------------------------------------------------------------------------- /chapter13/chapter-13-committing-everything-to-git-and-what-about-secrets/kustomize/.gitexplodee: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Implementing-GitOps-with-Kubernetes/123557c4905e8677fc4a6807aee28e146dcc192e/chapter13/chapter-13-committing-everything-to-git-and-what-about-secrets/kustomize/.gitexplodee -------------------------------------------------------------------------------- /chapter13/chapter-13-committing-everything-to-git-and-what-about-secrets/security/external-secrets/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: external-secrets 3 | version: 1.0.0 4 | description: This Chart deploys external-secrets secrets. 5 | dependencies: 6 | - name: external-secrets 7 | version: "0.9.13" 8 | repository: https://charts.external-secrets.io 9 | -------------------------------------------------------------------------------- /chapter13/chapter-13-committing-everything-to-git-and-what-about-secrets/security/external-secrets/values.yaml: -------------------------------------------------------------------------------- 1 | external-secrets: 2 | serviceMonitor: 3 | # -- Specifies whether to create a ServiceMonitor resource for collecting Prometheus metrics 4 | enabled: true 5 | -------------------------------------------------------------------------------- /chapter13/chapter-13-committing-everything-to-git-and-what-about-secrets/security/sealed-secrets/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: sealed-secrets 3 | version: 1.0.0 4 | description: This Chart deploys sealed secrets. 5 | dependencies: 6 | - name: sealed-secrets 7 | version: 2.15.0 8 | repository: https://bitnami-labs.github.io/sealed-secrets 9 | -------------------------------------------------------------------------------- /chapter13/chapter-13-committing-everything-to-git-and-what-about-secrets/security/sealed-secrets/values.yaml: -------------------------------------------------------------------------------- 1 | sealed-secrets: 2 | crd: 3 | create: false 4 | resources: 5 | requests: 6 | memory: 75Mi 7 | cpu: 75m 8 | limits: 9 | memory: 125Mi 10 | cpu: 250m 11 | podSecurityContext: 12 | seccompProfile: 13 | type: RuntimeDefault 14 | containerSecurityContext: 15 | seccompProfile: 16 | type: RuntimeDefault 17 | allowPrivilegeEscalation: false 18 | -------------------------------------------------------------------------------- /chapter13/chapter-13-hardening-declarative-gitops-cd-on-kubernetes /end_user_threat_model.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Implementing-GitOps-with-Kubernetes/123557c4905e8677fc4a6807aee28e146dcc192e/chapter13/chapter-13-hardening-declarative-gitops-cd-on-kubernetes /end_user_threat_model.pdf -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/applicationsets/security/kyverno-applicationset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: ApplicationSet 3 | metadata: 4 | name: kyverno 5 | namespace: argocd 6 | spec: 7 | generators: 8 | - clusters: 9 | selector: 10 | matchLabels: 11 | env: dev 12 | values: 13 | branch: development 14 | - clusters: 15 | selector: 16 | matchLabels: 17 | env: prod 18 | values: 19 | branch: main 20 | template: 21 | metadata: 22 | name: "{{name}}-kyverno" 23 | annotations: 24 | argocd.argoproj.io/manifest-generate-paths: ".;.." 25 | spec: 26 | project: default 27 | sources: 28 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git 29 | targetRevision: "{{values.branch}}" 30 | path: "./chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/kustomize/security/kyverno/policies/base" 31 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git 32 | targetRevision: "{{values.branch}}" 33 | path: "./chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/security/kyverno" 34 | helm: 35 | releaseName: "kyverno" 36 | valueFiles: 37 | - "values.yaml" 38 | destination: 39 | name: "{{name}}" 40 | namespace: "kyverno" 41 | syncPolicy: 42 | automated: 43 | prune: false 44 | selfHeal: true 45 | syncOptions: 46 | - CreateNamespace=true 47 | retry: 48 | limit: 5 49 | -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/kustomize/.gitexplodee: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Implementing-GitOps-with-Kubernetes/123557c4905e8677fc4a6807aee28e146dcc192e/chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/kustomize/.gitexplodee -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/kustomize/security/kyverno/policies/base/argocd-application-field-validation.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: kyverno.io/v1 3 | kind: ClusterPolicy 4 | metadata: 5 | name: application-field-validation 6 | annotations: 7 | policies.kyverno.io/title: Application Field Validation 8 | policies.kyverno.io/category: Argo 9 | policies.kyverno.io/severity: medium 10 | policies.kyverno.io/subject: Application 11 | kyverno.io/kyverno-version: 1.6.0 12 | policies.kyverno.io/minversion: 1.6.0 13 | kyverno.io/kubernetes-version: "1.23" 14 | policies.kyverno.io/description: >- 15 | This policy performs some best practices validation on Application fields. 16 | Path or chart must be specified but never both. And destination.name or 17 | destination.server must be specified but never both. 18 | spec: 19 | validationFailureAction: Audit 20 | background: true 21 | rules: 22 | - name: source-path-chart 23 | match: 24 | any: 25 | - resources: 26 | kinds: 27 | - Application 28 | validate: 29 | message: >- 30 | `spec.source.path` OR `spec.source.chart` should be specified but never both. 31 | anyPattern: 32 | - spec: 33 | source: 34 | path: '?*' 35 | X(chart): 36 | - spec: 37 | source: 38 | X(path): 39 | chart: '?*' 40 | - name: destination-server-name 41 | match: 42 | any: 43 | - resources: 44 | kinds: 45 | - Application 46 | validate: 47 | message: >- 48 | `spec.destination.server` OR `spec.destination.name` should be specified but never both. 49 | anyPattern: 50 | - spec: 51 | destination: 52 | server: '?*' 53 | X(name): 54 | - spec: 55 | destination: 56 | X(server): 57 | name: '?*' 58 | -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/kustomize/security/kyverno/policies/base/argocd-application-prevent-default-project.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: kyverno.io/v1 3 | kind: ClusterPolicy 4 | metadata: 5 | name: application-prevent-default-project 6 | annotations: 7 | policies.kyverno.io/title: Prevent Use of Default Project 8 | policies.kyverno.io/category: Argo 9 | policies.kyverno.io/severity: medium 10 | kyverno.io/kyverno-version: 1.6.2 11 | policies.kyverno.io/minversion: 1.6.0 12 | kyverno.io/kubernetes-version: "1.23" 13 | policies.kyverno.io/subject: Application 14 | policies.kyverno.io/description: >- 15 | This policy prevents the use of the default project in an Application. 16 | spec: 17 | validationFailureAction: Audit 18 | background: true 19 | rules: 20 | - name: default-project 21 | match: 22 | any: 23 | - resources: 24 | kinds: 25 | - Application 26 | preconditions: 27 | all: 28 | - key: "{{ request.operation || 'BACKGROUND' }}" 29 | operator: NotEquals 30 | value: DELETE 31 | validate: 32 | message: "The default project may not be used in an Application." 33 | pattern: 34 | spec: 35 | project: "!default" 36 | -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/kustomize/security/kyverno/policies/base/argocd-application-prevent-updates-project.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: kyverno.io/v1 3 | kind: ClusterPolicy 4 | metadata: 5 | name: application-prevent-updates-project 6 | annotations: 7 | policies.kyverno.io/title: Prevent Updates to Project 8 | policies.kyverno.io/category: Argo 9 | policies.kyverno.io/severity: medium 10 | kyverno.io/kyverno-version: 1.6.2 11 | policies.kyverno.io/minversion: 1.6.0 12 | kyverno.io/kubernetes-version: "1.23" 13 | policies.kyverno.io/subject: Application 14 | policies.kyverno.io/description: >- 15 | This policy prevents updates to the project field after an Application is created. 16 | spec: 17 | validationFailureAction: Audit 18 | background: true 19 | rules: 20 | - name: project-updates 21 | match: 22 | any: 23 | - resources: 24 | kinds: 25 | - Application 26 | preconditions: 27 | all: 28 | - key: "{{ request.operation || 'BACKGROUND' }}" 29 | operator: Equals 30 | value: UPDATE 31 | validate: 32 | message: "The spec.project cannot be changed once the Application is created." 33 | deny: 34 | conditions: 35 | any: 36 | - key: "{{request.object.spec.project}}" 37 | operator: NotEquals 38 | value: "{{request.oldObject.spec.project}}" 39 | -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/kustomize/security/kyverno/policies/base/disallow-container-sock-mounts.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: kyverno.io/v1 3 | kind: ClusterPolicy 4 | metadata: 5 | name: disallow-container-sock-mounts 6 | annotations: 7 | policies.kyverno.io/title: Disallow CRI socket mounts 8 | policies.kyverno.io/category: Best Practices, EKS Best Practices 9 | policies.kyverno.io/severity: medium 10 | policies.kyverno.io/subject: Pod 11 | policies.kyverno.io/minversion: 1.6.0 12 | policies.kyverno.io/description: >- 13 | Container daemon socket bind mounts allows access to the container engine on the 14 | node. This access can be used for privilege escalation and to manage containers 15 | outside of Kubernetes, and hence should not be allowed. This policy validates that 16 | the sockets used for CRI engines Docker, Containerd, and CRI-O are not used. 17 | spec: 18 | validationFailureAction: Audit 19 | background: true 20 | rules: 21 | - name: validate-docker-sock-mount 22 | match: 23 | any: 24 | - resources: 25 | kinds: 26 | - Pod 27 | validate: 28 | message: "Use of the Docker Unix socket is not allowed." 29 | pattern: 30 | spec: 31 | =(volumes): 32 | - =(hostPath): 33 | path: "!/var/run/docker.sock" 34 | - name: validate-containerd-sock-mount 35 | match: 36 | any: 37 | - resources: 38 | kinds: 39 | - Pod 40 | validate: 41 | message: "Use of the Containerd Unix socket is not allowed." 42 | pattern: 43 | spec: 44 | =(volumes): 45 | - =(hostPath): 46 | path: "!/var/run/containerd.sock" 47 | - name: validate-crio-sock-mount 48 | match: 49 | any: 50 | - resources: 51 | kinds: 52 | - Pod 53 | validate: 54 | message: "Use of the CRI-O Unix socket is not allowed." 55 | pattern: 56 | spec: 57 | =(volumes): 58 | - =(hostPath): 59 | path: "!/var/run/crio.sock" 60 | -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/kustomize/security/kyverno/policies/base/disallow-empty-ingress-host.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: kyverno.io/v1 3 | kind: ClusterPolicy 4 | metadata: 5 | name: disallow-empty-ingress-host 6 | annotations: 7 | policies.kyverno.io/title: Disallow empty Ingress host 8 | policies.kyverno.io/category: Best Practices 9 | policies.kyverno.io/minversion: 1.6.0 10 | policies.kyverno.io/severity: medium 11 | policies.kyverno.io/subject: Ingress 12 | policies.kyverno.io/description: >- 13 | An ingress resource needs to define an actual host name 14 | in order to be valid. This policy ensures that there is a 15 | hostname for each rule defined. 16 | spec: 17 | validationFailureAction: Audit 18 | background: true 19 | rules: 20 | - name: disallow-empty-ingress-host 21 | match: 22 | any: 23 | - resources: 24 | kinds: 25 | - Ingress 26 | validate: 27 | message: "The Ingress host name must be defined, not empty." 28 | deny: 29 | conditions: 30 | all: 31 | - key: "{{ request.object.spec.rules[].host || `[]` | length(@) }}" 32 | operator: NotEquals 33 | value: "{{ request.object.spec.rules[].http || `[]` | length(@) }}" 34 | -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/kustomize/security/kyverno/policies/base/disallow-latest-tag.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: kyverno.io/v1 3 | kind: ClusterPolicy 4 | metadata: 5 | name: disallow-latest-tag 6 | annotations: 7 | policies.kyverno.io/title: Disallow Latest Tag 8 | policies.kyverno.io/category: Best Practices 9 | policies.kyverno.io/minversion: 1.6.0 10 | policies.kyverno.io/severity: medium 11 | policies.kyverno.io/subject: Pod 12 | policies.kyverno.io/description: >- 13 | The ':latest' tag is mutable and can lead to unexpected errors if the 14 | image changes. A best practice is to use an immutable tag that maps to 15 | a specific version of an application Pod. This policy validates that the image 16 | specifies a tag and that it is not called `latest`. 17 | spec: 18 | validationFailureAction: Audit 19 | background: true 20 | rules: 21 | - name: require-image-tag 22 | match: 23 | any: 24 | - resources: 25 | kinds: 26 | - Pod 27 | validate: 28 | message: "An image tag is required." 29 | pattern: 30 | spec: 31 | containers: 32 | - image: "*:*" 33 | - name: validate-image-tag 34 | match: 35 | any: 36 | - resources: 37 | kinds: 38 | - Pod 39 | validate: 40 | message: "Using a mutable image tag e.g. 'latest' is not allowed." 41 | pattern: 42 | spec: 43 | containers: 44 | - image: "!*:latest" 45 | -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/kustomize/security/kyverno/policies/base/drop-all-capabilities.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: kyverno.io/v1 3 | kind: ClusterPolicy 4 | metadata: 5 | name: drop-all-capabilities 6 | annotations: 7 | policies.kyverno.io/title: Drop All Capabilities 8 | policies.kyverno.io/category: Best Practices 9 | policies.kyverno.io/severity: medium 10 | policies.kyverno.io/minversion: 1.6.0 11 | policies.kyverno.io/subject: Pod 12 | policies.kyverno.io/description: >- 13 | Capabilities permit privileged actions without giving full root access. All 14 | capabilities should be dropped from a Pod, with only those required added back. 15 | This policy ensures that all containers explicitly specify the `drop: ["ALL"]` 16 | ability. Note that this policy also illustrates how to cover drop entries in any 17 | case although this may not strictly conform to the Pod Security Standards. 18 | spec: 19 | validationFailureAction: Audit 20 | background: true 21 | rules: 22 | - name: require-drop-all 23 | match: 24 | any: 25 | - resources: 26 | kinds: 27 | - Pod 28 | preconditions: 29 | all: 30 | - key: "{{ request.operation || 'BACKGROUND' }}" 31 | operator: NotEquals 32 | value: DELETE 33 | validate: 34 | message: >- 35 | Containers must drop `ALL` capabilities. 36 | foreach: 37 | - list: request.object.spec.[ephemeralContainers, initContainers, containers][] 38 | deny: 39 | conditions: 40 | all: 41 | - key: ALL 42 | operator: AnyNotIn 43 | value: "{{ element.securityContext.capabilities.drop[].to_upper(@) || `[]` }}" 44 | -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/kustomize/security/kyverno/policies/base/drop-cap-net-raw.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: kyverno.io/v1 3 | kind: ClusterPolicy 4 | metadata: 5 | name: drop-cap-net-raw 6 | annotations: 7 | policies.kyverno.io/title: Drop CAP_NET_RAW 8 | policies.kyverno.io/category: Best Practices 9 | policies.kyverno.io/minversion: 1.6.0 10 | policies.kyverno.io/severity: medium 11 | policies.kyverno.io/subject: Pod 12 | policies.kyverno.io/description: >- 13 | Capabilities permit privileged actions without giving full root access. The 14 | CAP_NET_RAW capability, enabled by default, allows processes in a container to 15 | forge packets and bind to any interface potentially leading to MitM attacks. 16 | This policy ensures that all containers explicitly drop the CAP_NET_RAW 17 | ability. Note that this policy also illustrates how to cover drop entries in any 18 | case although this may not strictly conform to the Pod Security Standards. 19 | spec: 20 | validationFailureAction: Audit 21 | background: true 22 | rules: 23 | - name: require-drop-cap-net-raw 24 | match: 25 | any: 26 | - resources: 27 | kinds: 28 | - Pod 29 | preconditions: 30 | all: 31 | - key: "{{ request.operation || 'BACKGROUND' }}" 32 | operator: NotEquals 33 | value: DELETE 34 | validate: 35 | message: >- 36 | Containers must drop the `CAP_NET_RAW` capability. 37 | foreach: 38 | - list: request.object.spec.[ephemeralContainers, initContainers, containers][] 39 | deny: 40 | conditions: 41 | all: 42 | - key: CAP_NET_RAW 43 | operator: AnyNotIn 44 | value: "{{ element.securityContext.capabilities.drop[].to_upper(@) || `[]` }}" 45 | -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/kustomize/security/kyverno/policies/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - argocd-application-field-validation.yaml 5 | - argocd-application-prevent-default-project.yaml 6 | - argocd-application-prevent-updates-project.yaml 7 | - disallow-container-sock-mounts.yaml 8 | - disallow-empty-ingress-host.yaml 9 | - disallow-latest-tag.yaml 10 | - drop-all-capabilities.yaml 11 | - drop-cap-net-raw.yaml 12 | - require-labels.yaml 13 | - require-pod-probes.yaml 14 | - require-requests-limits.yaml 15 | - require-ro-rootfs.yaml 16 | - restrict-deprecated-registry.yaml 17 | - restrict-nodeport.yaml 18 | -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/kustomize/security/kyverno/policies/base/require-labels.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: kyverno.io/v1 3 | kind: ClusterPolicy 4 | metadata: 5 | name: require-labels 6 | annotations: 7 | policies.kyverno.io/title: Require Labels 8 | policies.kyverno.io/category: Best Practices 9 | policies.kyverno.io/minversion: 1.6.0 10 | policies.kyverno.io/severity: medium 11 | policies.kyverno.io/subject: Pod, Label 12 | policies.kyverno.io/description: >- 13 | Define and use labels that identify semantic attributes of your application or Deployment. 14 | A common set of labels allows tools to work collaboratively, describing objects in a common manner that 15 | all tools can understand. The recommended labels describe applications in a way that can be 16 | queried. This policy validates that the label `app.kubernetes.io/name` is specified with some value. 17 | spec: 18 | validationFailureAction: Audit 19 | background: true 20 | rules: 21 | - name: check-for-labels 22 | match: 23 | any: 24 | - resources: 25 | kinds: 26 | - Pod 27 | validate: 28 | message: "The label `app.kubernetes.io/name` is required." 29 | pattern: 30 | metadata: 31 | labels: 32 | app.kubernetes.io/name: "?*" 33 | -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/kustomize/security/kyverno/policies/base/require-pod-probes.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: kyverno.io/v1 3 | kind: ClusterPolicy 4 | metadata: 5 | name: require-pod-probes 6 | annotations: 7 | pod-policies.kyverno.io/autogen-controllers: DaemonSet,Deployment,StatefulSet 8 | policies.kyverno.io/title: Require Pod Probes 9 | policies.kyverno.io/category: Best Practices, EKS Best Practices 10 | policies.kyverno.io/severity: medium 11 | policies.kyverno.io/subject: Pod 12 | policies.kyverno.io/description: >- 13 | Liveness and readiness probes need to be configured to correctly manage a Pod's 14 | lifecycle during deployments, restarts, and upgrades. For each Pod, a periodic 15 | `livenessProbe` is performed by the kubelet to determine if the Pod's containers 16 | are running or need to be restarted. A `readinessProbe` is used by Services 17 | and Deployments to determine if the Pod is ready to receive network traffic. 18 | This policy validates that all containers have one of livenessProbe, readinessProbe, 19 | or startupProbe defined. 20 | spec: 21 | validationFailureAction: Audit 22 | background: true 23 | rules: 24 | - name: validate-probes 25 | match: 26 | any: 27 | - resources: 28 | kinds: 29 | - Pod 30 | preconditions: 31 | all: 32 | - key: "{{request.operation || 'BACKGROUND'}}" 33 | operator: AnyIn 34 | value: 35 | - CREATE 36 | - UPDATE 37 | validate: 38 | message: "Liveness, readiness, or startup probes are required for all containers." 39 | foreach: 40 | - list: request.object.spec.containers[] 41 | deny: 42 | conditions: 43 | all: 44 | - key: livenessProbe 45 | operator: AllNotIn 46 | value: "{{ element.keys(@)[] }}" 47 | - key: startupProbe 48 | operator: AllNotIn 49 | value: "{{ element.keys(@)[] }}" 50 | - key: readinessProbe 51 | operator: AllNotIn 52 | value: "{{ element.keys(@)[] }}" 53 | -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/kustomize/security/kyverno/policies/base/require-requests-limits.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: kyverno.io/v1 3 | kind: ClusterPolicy 4 | metadata: 5 | name: require-requests-limits 6 | annotations: 7 | policies.kyverno.io/title: Require Limits and Requests 8 | policies.kyverno.io/category: Best Practices, EKS Best Practices 9 | policies.kyverno.io/severity: medium 10 | policies.kyverno.io/subject: Pod 11 | policies.kyverno.io/minversion: 1.6.0 12 | policies.kyverno.io/description: >- 13 | As application workloads share cluster resources, it is important to limit resources 14 | requested and consumed by each Pod. It is recommended to require resource requests and 15 | limits per Pod, especially for memory and CPU. If a Namespace level request or limit is specified, 16 | defaults will automatically be applied to each Pod based on the LimitRange configuration. 17 | This policy validates that all containers have something specified for memory and CPU 18 | requests and memory limits. 19 | spec: 20 | validationFailureAction: Audit 21 | background: true 22 | rules: 23 | - name: validate-resources 24 | match: 25 | any: 26 | - resources: 27 | kinds: 28 | - Pod 29 | validate: 30 | message: "CPU and memory resource requests and limits are required." 31 | pattern: 32 | spec: 33 | containers: 34 | - resources: 35 | requests: 36 | memory: "?*" 37 | cpu: "?*" 38 | limits: 39 | memory: "?*" 40 | -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/kustomize/security/kyverno/policies/base/require-ro-rootfs.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: kyverno.io/v1 3 | kind: ClusterPolicy 4 | metadata: 5 | name: require-ro-rootfs 6 | annotations: 7 | policies.kyverno.io/title: Require Read-Only Root Filesystem 8 | policies.kyverno.io/category: Best Practices, EKS Best Practices, PSP Migration 9 | policies.kyverno.io/severity: medium 10 | policies.kyverno.io/subject: Pod 11 | policies.kyverno.io/minversion: 1.6.0 12 | policies.kyverno.io/description: >- 13 | A read-only root file system helps to enforce an immutable infrastructure strategy; 14 | the container only needs to write on the mounted volume that persists the state. 15 | An immutable root filesystem can also prevent malicious binaries from writing to the 16 | host system. This policy validates that containers define a securityContext 17 | with `readOnlyRootFilesystem: true`. 18 | spec: 19 | validationFailureAction: Audit 20 | background: true 21 | rules: 22 | - name: validate-readOnlyRootFilesystem 23 | match: 24 | any: 25 | - resources: 26 | kinds: 27 | - Pod 28 | validate: 29 | message: "Root filesystem must be read-only." 30 | pattern: 31 | spec: 32 | containers: 33 | - securityContext: 34 | readOnlyRootFilesystem: true 35 | -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/kustomize/security/kyverno/policies/base/restrict-deprecated-registry.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: kyverno.io/v1 3 | kind: ClusterPolicy 4 | metadata: 5 | name: restrict-deprecated-registry 6 | annotations: 7 | policies.kyverno.io/title: Restrict Deprecated Registry 8 | policies.kyverno.io/category: Best Practices, EKS Best Practices 9 | policies.kyverno.io/severity: high 10 | policies.kyverno.io/minversion: 1.9.0 11 | policies.kyverno.io/subject: Pod 12 | policies.kyverno.io/description: >- 13 | Legacy k8s.gcr.io container image registry will be frozen in early April 2023 14 | k8s.gcr.io image registry will be frozen from the 3rd of April 2023. 15 | Images for Kubernetes 1.27 will not be available in the k8s.gcr.io image registry. 16 | Please read our announcement for more details. 17 | https://kubernetes.io/blog/2023/02/06/k8s-gcr-io-freeze-announcement/ 18 | spec: 19 | validationFailureAction: Audit 20 | # validationFailureAction: Audit 21 | background: true 22 | rules: 23 | - name: restrict-deprecated-registry 24 | match: 25 | any: 26 | - resources: 27 | kinds: 28 | - Pod 29 | validate: 30 | message: 'The "k8s.gcr.io" image registry is deprecated. "registry.k8s.io" should now be used.' 31 | foreach: 32 | - list: "request.object.spec.[initContainers, ephemeralContainers, containers][]" 33 | deny: 34 | conditions: 35 | all: 36 | - key: "{{ element.image }}" 37 | operator: Equals 38 | value: "k8s.gcr.io/*" 39 | -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/kustomize/security/kyverno/policies/base/restrict-nodeport.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: kyverno.io/v1 3 | kind: ClusterPolicy 4 | metadata: 5 | name: restrict-nodeport 6 | annotations: 7 | policies.kyverno.io/title: Disallow NodePort 8 | policies.kyverno.io/category: Best Practices 9 | policies.kyverno.io/minversion: 1.6.0 10 | policies.kyverno.io/severity: medium 11 | policies.kyverno.io/subject: Service 12 | policies.kyverno.io/description: >- 13 | A Kubernetes Service of type NodePort uses a host port to receive traffic from 14 | any source. A NetworkPolicy cannot be used to control traffic to host ports. 15 | Although NodePort Services can be useful, their use must be limited to Services 16 | with additional upstream security checks. This policy validates that any new Services 17 | do not use the `NodePort` type. 18 | spec: 19 | validationFailureAction: Audit 20 | background: true 21 | rules: 22 | - name: validate-nodeport 23 | match: 24 | any: 25 | - resources: 26 | kinds: 27 | - Service 28 | validate: 29 | message: "Services of type NodePort are not allowed." 30 | pattern: 31 | spec: 32 | =(type): "!NodePort" 33 | -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/security/kyverno/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: kyverno 3 | version: 1.0.0 4 | description: This Chart deploys kyverno. 5 | dependencies: 6 | - name: kyverno 7 | version: 3.1.4 8 | repository: https://kyverno.github.io/kyverno/ 9 | -------------------------------------------------------------------------------- /chapter13/chapter-13-leveraging-a-policy-engine-for-policy-as-code-practices/security/kyverno/values.yaml: -------------------------------------------------------------------------------- 1 | kyverno: 2 | # CRDs configuration 3 | crds: 4 | # -- Whether to have Helm install the Kyverno CRDs, if the CRDs are not installed by Helm, they must be added before policies can be created 5 | install: true 6 | annotations: 7 | argocd.argoproj.io/sync-options: Replace=true 8 | strategy.spinnaker.io/replace: "true" 9 | -------------------------------------------------------------------------------- /chapter14/chapter-14-forecasting-and-monitoring-costs-with-gitops/applicationsets/optimization/opencost-applicationset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: ApplicationSet 3 | metadata: 4 | name: opencost 5 | namespace: argocd 6 | spec: 7 | generators: 8 | - clusters: 9 | selector: 10 | matchLabels: 11 | env: dev 12 | values: 13 | branch: development 14 | - clusters: 15 | selector: 16 | matchLabels: 17 | env: prod 18 | values: 19 | branch: main 20 | template: 21 | metadata: 22 | name: "{{name}}-opencost" 23 | annotations: 24 | argocd.argoproj.io/manifest-generate-paths: ".;.." 25 | spec: 26 | project: default 27 | sources: 28 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git 29 | targetRevision: main 30 | ref: valuesRepo 31 | - repoURL: git@github.com:PacktPublishing/Implementing-GitOps-with-Kubernetes.git 32 | targetRevision: "{{values.branch}}" 33 | path: "./chapter14/chapter-14-forecasting-and-monitoring-costs-with-gitops/optimization/opencost" 34 | helm: 35 | releaseName: "opencost" 36 | valueFiles: 37 | - "values.yaml" 38 | - $valuesRepo/chapter14/chapter-14-forecasting-and-monitoring-costs-with-gitops/cluster/{{name}}/optimization/opencost/values.yaml 39 | destination: 40 | name: "{{name}}" 41 | namespace: "opencost" 42 | syncPolicy: 43 | automated: 44 | prune: false 45 | selfHeal: true 46 | syncOptions: 47 | - CreateNamespace=true 48 | retry: 49 | limit: 5 50 | -------------------------------------------------------------------------------- /chapter14/chapter-14-forecasting-and-monitoring-costs-with-gitops/cluster/in-cluster-austria/optimization/opencost/values.yaml: -------------------------------------------------------------------------------- 1 | opencost: 2 | opencost: 3 | customPricing: 4 | # -- Enables custom pricing for on-premise setup. 5 | enabled: true 6 | configmapName: custom-pricing-model 7 | # -- Path for the pricing configuration. 8 | configPath: /tmp/custom-config 9 | # -- Configures the pricing model provided in the values file. 10 | createConfigmap: true 11 | # -- More information about these values here: https://www.opencost.io/docs/configuration/on-prem#custom-pricing-using-the-opencost-helm-chart 12 | costModel: 13 | description: Modified prices based on your internal pricing 14 | CPU: 1.0 15 | spotCPU: 0.006900 16 | RAM: 0.75 17 | spotRAM: 0.000999 18 | GPU: 1.25 19 | storage: 0.2 20 | zoneNetworkEgress: 0.02 21 | regionNetworkEgress: 0.02 22 | internetNetworkEgress: 0.15 23 | -------------------------------------------------------------------------------- /chapter14/chapter-14-forecasting-and-monitoring-costs-with-gitops/cluster/in-cluster-germany/optimization/opencost/values.yaml: -------------------------------------------------------------------------------- 1 | opencost: 2 | opencost: 3 | customPricing: 4 | # -- Enables custom pricing for on-premise setup. 5 | enabled: true 6 | configmapName: custom-pricing-model 7 | # -- Path for the pricing configuration. 8 | configPath: /tmp/custom-config 9 | # -- Configures the pricing model provided in the values file. 10 | createConfigmap: true 11 | # -- More information about these values here: https://www.opencost.io/docs/configuration/on-prem#custom-pricing-using-the-opencost-helm-chart 12 | costModel: 13 | description: Modified prices based on your internal pricing 14 | CPU: 1.25 15 | spotCPU: 0.006655 16 | RAM: 0.50 17 | spotRAM: 0.000892 18 | GPU: 0.95 19 | storage: 0.25 20 | zoneNetworkEgress: 0.01 21 | regionNetworkEgress: 0.01 22 | internetNetworkEgress: 0.12 23 | -------------------------------------------------------------------------------- /chapter14/chapter-14-forecasting-and-monitoring-costs-with-gitops/cluster/in-cluster-ireland/optimization/opencost/values.yaml: -------------------------------------------------------------------------------- 1 | opencost: 2 | opencost: 3 | customPricing: 4 | # -- Enables custom pricing for on-premise setup. 5 | enabled: true 6 | configmapName: custom-pricing-model 7 | # -- Path for the pricing configuration. 8 | configPath: /tmp/custom-config 9 | # -- Configures the pricing model provided in the values file. 10 | createConfigmap: true 11 | # -- More information about these values here: https://www.opencost.io/docs/configuration/on-prem#custom-pricing-using-the-opencost-helm-chart 12 | costModel: 13 | description: Modified prices based on your internal pricing 14 | CPU: 1.5 15 | spotCPU: 0.01023 16 | RAM: 0.75 17 | spotRAM: 0.00103 18 | GPU: 0.8 19 | storage: 0.3 20 | zoneNetworkEgress: 0.05 21 | regionNetworkEgress: 0.05 22 | internetNetworkEgress: 0.10 23 | -------------------------------------------------------------------------------- /chapter14/chapter-14-forecasting-and-monitoring-costs-with-gitops/optimization/kubecost/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: kubecost 3 | version: 1.0.0 4 | description: This Chart deploys kubecost. 5 | dependencies: 6 | - name: cost-analyzer 7 | version: 2.3.0 8 | repository: https://kubecost.github.io/cost-analyzer/ 9 | -------------------------------------------------------------------------------- /chapter14/chapter-14-forecasting-and-monitoring-costs-with-gitops/optimization/kubecost/README.md: -------------------------------------------------------------------------------- 1 | # Kubecost 2 | 3 | In this simple example, I will show you how to set an alert via the UI, configure a budget with an alert via the UI, and how to set it up through the `values.yaml` in the Helm Chart for deployment via tools like Argo CD. 4 | 5 | Let's consider a straightforward use case: 6 | 7 | - **Project Budget**: $100 per month for Project X. 8 | - **Cluster Budget**: $500 per month for the entire cluster. 9 | - **Notification**: If any budget is exceeded, a notification should be sent to the FinOps team members. 10 | 11 | The goal of the FinOps team is not only to track the project but also to respond promptly when costs are too high, understand these costs, and identify cost drivers without needing to be Kubernetes experts. 12 | 13 | ## Kubecost UI 14 | 15 | If you haven't deployed Kubecost yet, there's a demo environment available that allows you to configure and test it. You can access this environment at https://demo.kubecost.io/. 16 | 17 | We will use this demo environment to configure an alert. 18 | 19 | ### Setup Alert in the Kubecost UI 20 | 21 | To set up alerts in the Kubecost UI, follow these steps: 22 | 23 | 1. **Navigate to the Alerts Section**: Open the Kubecost UI and go to the Alerts section. 24 | 2. **Create a New Alert**: Click on the "+ Create Alert" button. 25 | 3. **Configure Alert Parameters**: Select the type of alert you want to set up (e.g., budget, efficiency). Define the parameters such as the time window, aggregation level, filter criteria, and threshold. 26 | 4. **Set Notification Channels**: Specify the notification channels, such as email, Slack, or Microsoft Teams, by providing the necessary webhook URLs and email addresses. 27 | 5. **Save the Alert**: Review the configuration and save the alert. 28 | 29 | Here is an example of how you can set up an alert for a alerts in the Kubecost UI: 30 | 31 | ![Kubecost UI Setup Alert](images/kubecost_ui_setup_alert.gif) 32 | 33 | ### Setup Budget + Alert in the Kubecost UI 34 | 35 | To set up a budget with an alert in the Kubecost UI: 36 | 37 | 1. **Navigate to the Budgets Section**: Open the Kubecost UI and go to the Budgets section. 38 | 2. **Create a New Budget**: Click on the "+ Create Budget" button. 39 | 3. **Define Budget Parameters**: Enter the budget amount, select the time window (e.g., daily, weekly, monthly), and choose the aggregation level (e.g., namespace, cluster). 40 | 4. **Add Alert for the Budget**: Link the budget to an alert by configuring the alert settings. Specify the threshold at which the alert should trigger. 41 | 5. **Set Notification Channels**: Provide email addresses and webhook URLs for Slack or Microsoft Teams to receive notifications. 42 | 6. **Save the Budget and Alert**: Review the setup and save both the budget and the alert. 43 | 44 | Here is an example of how you can set up an alert for a budget + alert in the Kubecost UI: 45 | 46 | ![Kubecost UI Setup Budget + Alert](images/kubecost_ui_budget_setup_alert.gif) 47 | 48 | ## Kubecost Helm Chart 49 | 50 | Kubecost can be deployed using the Helm Chart, which allows you to configure alerts and budgets using the `values.yaml` file. This approach is useful for setting up alerts as code and deploying them using GitOps tools like Argo CD. 51 | 52 | ### Setup Alert over values.yaml 53 | 54 | To set up alerts using the `values.yaml` file in the Kubecost Helm Chart: 55 | 56 | 1. **Edit values.yaml**: Open the `values.yaml` file in your preferred text editor. 57 | 2. **Add Alert Configuration**: Insert the alert configuration under the `alerts` section. Below is an example configuration: 58 | 59 | ```yaml 60 | --- 61 | alerts: 62 | - type: budget 63 | threshold: 100 64 | window: 30d 65 | aggregation: namespace 66 | filter: project-x 67 | ownerContact: 68 | - owner@example.com 69 | slackWebhookUrl: https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX 70 | msTeamsWebhookUrl: https://xxxxx.webhook.office.com/webhookb2/XXXXXXXXXXXXXXXXXXXXXXXX/IncomingWebhook/XXXXXXXXXXXXXXXXXXXXXXXX 71 | - type: budget 72 | threshold: 500 73 | window: 30d 74 | aggregation: cluster 75 | filter: cluster-one 76 | ``` 77 | 78 | 3. **Apply the Helm Chart**: Deploy or update the Helm chart with the modified `values.yaml` file by running the following command: 79 | ```sh 80 | helm upgrade --install kubecost kubecost/cost-analyzer -f values.yaml -n kubecost --create-namespace 81 | ``` 82 | 4. **Verify Alerts**: After deployment, verify that the alerts are correctly set up by checking the Kubecost UI or using the `kubectl` command to inspect the configuration. 83 | 84 | This configuration helps the FinOps team monitor and control the spending across different namespaces and clusters, ensuring they stay within the defined budgets and can take action if spending exceeds the limits or understand the cost drivers. 85 | -------------------------------------------------------------------------------- /chapter14/chapter-14-forecasting-and-monitoring-costs-with-gitops/optimization/kubecost/images/kubecost_ui_budget_setup_alert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Implementing-GitOps-with-Kubernetes/123557c4905e8677fc4a6807aee28e146dcc192e/chapter14/chapter-14-forecasting-and-monitoring-costs-with-gitops/optimization/kubecost/images/kubecost_ui_budget_setup_alert.gif -------------------------------------------------------------------------------- /chapter14/chapter-14-forecasting-and-monitoring-costs-with-gitops/optimization/kubecost/images/kubecost_ui_setup_alert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Implementing-GitOps-with-Kubernetes/123557c4905e8677fc4a6807aee28e146dcc192e/chapter14/chapter-14-forecasting-and-monitoring-costs-with-gitops/optimization/kubecost/images/kubecost_ui_setup_alert.gif -------------------------------------------------------------------------------- /chapter14/chapter-14-forecasting-and-monitoring-costs-with-gitops/optimization/kubecost/values.yaml: -------------------------------------------------------------------------------- 1 | cost-analyzer: 2 | global: 3 | notifications: 4 | alertConfigs: 5 | frontendUrl: http://localhost:9090 6 | alerts: 7 | - type: budget 8 | threshold: 100 9 | window: 30d 10 | aggregation: namespace 11 | filter: projec-x 12 | ownerContact: 13 | - owner@example.com 14 | # optional, used for alert-specific Slack and Microsoft Teams alerts 15 | slackWebhookUrl: https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXXHALLO12 16 | msTeamsWebhookUrl: https://xxxxx.webhook.office.com/webhookb2/XXXXXXXXXXXXXXXXXXXXXXXX/IncomingWebhook/XXXXXXXXXXXXXXXXXXXXXXXX 17 | - type: budget 18 | threshold: 500 19 | window: 30d 20 | aggregation: cluster 21 | filter: cluster-one 22 | -------------------------------------------------------------------------------- /chapter14/chapter-14-forecasting-and-monitoring-costs-with-gitops/optimization/opencost/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: opencost 3 | version: 1.0.0 4 | description: This Chart deploys opencost. 5 | dependencies: 6 | - name: opencost 7 | version: 1.30.0 8 | repository: https://opencost.github.io/opencost-helm-chart 9 | -------------------------------------------------------------------------------- /chapter14/chapter-14-forecasting-and-monitoring-costs-with-gitops/optimization/opencost/values.yaml: -------------------------------------------------------------------------------- 1 | opencost: 2 | opencost: 3 | serviceAccount: 4 | # -- Specifies whether a service account should be created 5 | create: true 6 | -------------------------------------------------------------------------------- /pod-gitops-terraform-automation-tf-runner.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | creationTimestamp: "2024-04-20T14:49:50Z" 5 | labels: 6 | app.kubernetes.io/created-by: tf-controller 7 | app.kubernetes.io/instance: tf-runner-dbc4ede1 8 | app.kubernetes.io/name: tf-runner 9 | infra.contrib.fluxcd.io/terraform: flux-system 10 | tf.weave.works/tls-secret-name: terraform-runner.tls-1713710973 11 | name: gitops-terraform-automation-tf-runner 12 | namespace: flux-system 13 | resourceVersion: "22441" 14 | uid: b2010fda-ea85-465e-b93b-df1c0dedad7e 15 | spec: 16 | containers: 17 | - args: 18 | - --grpc-port 19 | - "30000" 20 | - --tls-secret-name 21 | - terraform-runner.tls-1713710973 22 | - --grpc-max-message-size 23 | - "30" 24 | env: 25 | - name: ARM_CLIENT_ID 26 | valueFrom: 27 | secretKeyRef: 28 | key: ARM_CLIENT_ID 29 | name: azure-creds 30 | - name: ARM_CLIENT_SECRET 31 | valueFrom: 32 | secretKeyRef: 33 | key: ARM_CLIENT_SECRET 34 | name: azure-creds 35 | - name: ARM_TENANT_ID 36 | valueFrom: 37 | secretKeyRef: 38 | key: ARM_TENANT_ID 39 | name: azure-creds 40 | - name: POD_NAME 41 | valueFrom: 42 | fieldRef: 43 | apiVersion: v1 44 | fieldPath: metadata.name 45 | - name: POD_NAMESPACE 46 | valueFrom: 47 | fieldRef: 48 | apiVersion: v1 49 | fieldPath: metadata.namespace 50 | - name: ARM_SUBSCRIPTION_ID 51 | valueFrom: 52 | secretKeyRef: 53 | key: ARM_SUBSCRIPTION_ID 54 | name: azure-creds 55 | image: mcr.microsoft.com/azure-cli 56 | imagePullPolicy: IfNotPresent 57 | name: tf-runner 58 | ports: 59 | - containerPort: 30000 60 | name: grpc 61 | protocol: TCP 62 | resources: {} 63 | securityContext: 64 | allowPrivilegeEscalation: false 65 | capabilities: 66 | drop: 67 | - ALL 68 | readOnlyRootFilesystem: true 69 | runAsNonRoot: true 70 | runAsUser: 65532 71 | seccompProfile: 72 | type: RuntimeDefault 73 | terminationMessagePath: /dev/termination-log 74 | terminationMessagePolicy: File 75 | volumeMounts: 76 | - mountPath: /tmp 77 | name: temp 78 | - mountPath: /home/runner 79 | name: home 80 | - mountPath: /var/run/secrets/kubernetes.io/serviceaccount 81 | name: kube-api-access-6xcws 82 | readOnly: true 83 | dnsPolicy: ClusterFirst 84 | enableServiceLinks: true 85 | nodeName: local-flux-cluster 86 | preemptionPolicy: PreemptLowerPriority 87 | priority: 0 88 | restartPolicy: Always 89 | schedulerName: default-scheduler 90 | securityContext: {} 91 | serviceAccount: tf-runner 92 | serviceAccountName: tf-runner 93 | terminationGracePeriodSeconds: 30 94 | tolerations: 95 | - effect: NoExecute 96 | key: node.kubernetes.io/not-ready 97 | operator: Exists 98 | tolerationSeconds: 300 99 | - effect: NoExecute 100 | key: node.kubernetes.io/unreachable 101 | operator: Exists 102 | tolerationSeconds: 300 103 | volumes: 104 | - emptyDir: {} 105 | name: temp 106 | - emptyDir: {} 107 | name: home 108 | - name: kube-api-access-6xcws 109 | projected: 110 | defaultMode: 420 111 | sources: 112 | - serviceAccountToken: 113 | expirationSeconds: 3607 114 | path: token 115 | - configMap: 116 | items: 117 | - key: ca.crt 118 | path: ca.crt 119 | name: kube-root-ca.crt 120 | - downwardAPI: 121 | items: 122 | - fieldRef: 123 | apiVersion: v1 124 | fieldPath: metadata.namespace 125 | path: namespace 126 | status: 127 | conditions: 128 | - lastProbeTime: null 129 | lastTransitionTime: "2024-04-20T14:49:52Z" 130 | status: "True" 131 | type: PodReadyToStartContainers 132 | - lastProbeTime: null 133 | lastTransitionTime: "2024-04-20T14:49:50Z" 134 | status: "True" 135 | type: Initialized 136 | - lastProbeTime: null 137 | lastTransitionTime: "2024-04-20T14:49:50Z" 138 | message: 'containers with unready status: [tf-runner]' 139 | reason: ContainersNotReady 140 | status: "False" 141 | type: Ready 142 | - lastProbeTime: null 143 | lastTransitionTime: "2024-04-20T14:49:50Z" 144 | message: 'containers with unready status: [tf-runner]' 145 | reason: ContainersNotReady 146 | status: "False" 147 | type: ContainersReady 148 | - lastProbeTime: null 149 | lastTransitionTime: "2024-04-20T14:49:50Z" 150 | status: "True" 151 | type: PodScheduled 152 | containerStatuses: 153 | - containerID: docker://8304f5f9eb98b020d2e9ad3e1a59e8581ef2127ed67ae9eb6d9d6c714caab916 154 | image: mcr.microsoft.com/azure-cli:latest 155 | imageID: docker-pullable://mcr.microsoft.com/azure-cli@sha256:eb21a975e39a46f49b86ab517ea2916e146560c7844d0c488cf85eee8a38398d 156 | lastState: 157 | terminated: 158 | containerID: docker://8304f5f9eb98b020d2e9ad3e1a59e8581ef2127ed67ae9eb6d9d6c714caab916 159 | exitCode: 127 160 | finishedAt: "2024-04-20T14:51:28Z" 161 | message: 'failed to create task for container: failed to create shim task: 162 | OCI runtime create failed: runc create failed: unable to start container 163 | process: exec: "--grpc-port": executable file not found in $PATH: unknown' 164 | reason: ContainerCannotRun 165 | startedAt: "2024-04-20T14:51:28Z" 166 | name: tf-runner 167 | ready: false 168 | restartCount: 4 169 | started: false 170 | state: 171 | waiting: 172 | message: back-off 1m20s restarting failed container=tf-runner pod=gitops-terraform-automation-tf-runner_flux-system(b2010fda-ea85-465e-b93b-df1c0dedad7e) 173 | reason: CrashLoopBackOff 174 | hostIP: 192.168.58.2 175 | hostIPs: 176 | - ip: 192.168.58.2 177 | phase: Running 178 | podIP: 10.244.0.25 179 | podIPs: 180 | - ip: 10.244.0.25 181 | qosClass: BestEffort 182 | startTime: "2024-04-20T14:49:50Z" 183 | --------------------------------------------------------------------------------