├── README.md ├── apps ├── cert-manager │ ├── certificates.yaml │ ├── cluster-issuer.yaml │ └── secret.yaml ├── java-app-resize │ ├── binding.yaml │ ├── deployment-start.yaml │ ├── policy-v2.yaml │ └── policy.yaml ├── postgresql │ ├── database.yaml │ ├── policies.yaml │ ├── roles.yaml │ ├── sample-app │ │ ├── config.yaml │ │ └── deployment.yaml │ └── schema.yaml └── vault │ └── job.yaml ├── bootstrap-via-appset ├── bootstrap.yaml └── oci-helm.yaml └── bootstrap ├── Chart.yaml ├── templates ├── cert-manager.yaml └── projects.yaml ├── values.yaml └── values ├── atlas └── values.yaml ├── cert-manager └── values.yaml ├── postgresql └── values.yaml ├── vault-config-operator └── values.yaml └── vault └── values.yaml /README.md: -------------------------------------------------------------------------------- 1 | ## Kubernetes GitOps Configuration Example with Argo CD [![Twitter](https://img.shields.io/twitter/follow/piotr_minkowski.svg?style=social&logo=twitter&label=Follow%20Me)](https://twitter.com/piotr_minkowski) 2 | 3 | Here's the current structure of the repo (will be modified soon): 4 | ```shell 5 | ├── apps 6 | │ └── cert-manager 7 | │ ├── secrets.yaml 8 | │ ├── certificates.yaml 9 | │ └── cluster-issuer.yaml 10 | └── bootstrap 11 | ├── Chart.yaml 12 | ├── templates 13 | │ ├── cert-manager.yaml 14 | │ ├── postgresql.yaml 15 | │ ├── projects.yaml 16 | │ ├── vault.yaml 17 | │ └── vault-config-operator.yaml 18 | └── values.yaml 19 | ``` 20 | 21 | For now, it shows how to install and use in the GitOps way: 22 | 1. [cert-manager](https://cert-manager.io/) via Helm chart 23 | 24 | For more information you can refer to the articles: 25 | 1. [Testing GitOps on Virtual Kubernetes Clusters with ArgoCD](https://piotrminkowski.com/2023/06/29/testing-gitops-on-virtual-kubernetes-clusters-with-argocd/) 26 | 27 | ## Getting Started 28 | 29 | You can test the configuration on the virtual Kubernetes cluster using the [vcluster](https://www.vcluster.com/) tool. In order to that create the ArgoCD `ApplicationSet` based on the cluster generator: 30 | ```yaml 31 | apiVersion: argoproj.io/v1alpha1 32 | kind: ApplicationSet 33 | metadata: 34 | name: config 35 | namespace: argocd 36 | spec: 37 | generators: 38 | - clusters: # (1) 39 | selector: 40 | matchLabels: 41 | argocd.argoproj.io/secret-type: cluster 42 | template: 43 | metadata: 44 | name: '{{name}}-config-test' 45 | spec: 46 | destination: 47 | namespace: argocd 48 | server: https://kubernetes.default.svc 49 | project: default 50 | source: 51 | helm: 52 | parameters: 53 | - name: server 54 | value: '{{server}}' 55 | - name: project 56 | value: '{{metadata.labels.loft.sh/vcluster-instance-name}}' 57 | path: bootstrap 58 | repoURL: https://github.com/piomin/kubernetes-config-argocd.git 59 | targetRevision: '{{metadata.labels.loft.sh/vcluster-instance-name}}' 60 | syncPolicy: 61 | automated: {} 62 | syncOptions: 63 | - CreateNamespace=true 64 | ``` 65 | 66 | If you just want to test on the single cluster managed by ArgoCD you can create the following ArgoCD `Application`: 67 | ```yaml 68 | apiVersion: argoproj.io/v1alpha1 69 | kind: Application 70 | metadata: 71 | name: config 72 | namespace: argocd 73 | spec: 74 | destination: 75 | namespace: argocd 76 | server: https://kubernetes.default.svc 77 | project: default 78 | source: 79 | path: bootstrap 80 | repoURL: https://github.com/piomin/kubernetes-config-argocd.git 81 | targetRevision: HEAD 82 | syncPolicy: 83 | automated: {} 84 | syncOptions: 85 | - CreateNamespace=true 86 | ``` -------------------------------------------------------------------------------- /apps/cert-manager/certificates.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: Certificate 3 | metadata: 4 | name: secure-caller-cert 5 | spec: 6 | keystores: 7 | jks: 8 | passwordSecretRef: 9 | name: jks-password-secret 10 | key: password 11 | create: true 12 | issuerRef: 13 | name: ss-clusterissuer 14 | group: cert-manager.io 15 | kind: ClusterIssuer 16 | privateKey: 17 | algorithm: ECDSA 18 | size: 256 19 | dnsNames: 20 | - localhost 21 | - secure-caller 22 | secretName: secure-caller-cert 23 | commonName: localhost 24 | duration: 1h0m0s 25 | renewBefore: 5m0s -------------------------------------------------------------------------------- /apps/cert-manager/cluster-issuer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: ss-clusterissuer 5 | spec: 6 | selfSigned: {} -------------------------------------------------------------------------------- /apps/cert-manager/secret.yaml: -------------------------------------------------------------------------------- 1 | kind: Secret 2 | apiVersion: v1 3 | metadata: 4 | name: jks-password-secret 5 | data: 6 | password: MTIzNDU2 7 | type: Opaque -------------------------------------------------------------------------------- /apps/java-app-resize/binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | name: update-pod 5 | annotations: 6 | argocd.argoproj.io/sync-wave: "1" 7 | rules: 8 | - verbs: 9 | - patch 10 | - update 11 | apiGroups: 12 | - '' 13 | resources: 14 | - pods 15 | --- 16 | apiVersion: rbac.authorization.k8s.io/v1 17 | kind: RoleBinding 18 | metadata: 19 | name: kyverno 20 | annotations: 21 | argocd.argoproj.io/sync-wave: "1" 22 | subjects: 23 | - kind: ServiceAccount 24 | name: kyverno-background-controller 25 | namespace: kyverno 26 | roleRef: 27 | apiGroup: rbac.authorization.k8s.io 28 | kind: Role 29 | name: update-pod -------------------------------------------------------------------------------- /apps/java-app-resize/deployment-start.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: sample-kotlin-spring 5 | labels: 6 | app: sample-kotlin-spring 7 | annotations: 8 | argocd.argoproj.io/sync-wave: "1" 9 | spec: 10 | replicas: 2 11 | selector: 12 | matchLabels: 13 | app: sample-kotlin-spring 14 | template: 15 | metadata: 16 | labels: 17 | app: sample-kotlin-spring 18 | spec: 19 | containers: 20 | - name: sample-kotlin-spring 21 | image: quay.io/pminkows/sample-kotlin-spring:1.5.1.1 22 | ports: 23 | - containerPort: 8080 24 | readinessProbe: 25 | httpGet: 26 | path: /actuator/health/readiness 27 | port: 8080 28 | scheme: HTTP 29 | initialDelaySeconds: 10 30 | timeoutSeconds: 1 31 | periodSeconds: 5 32 | successThreshold: 1 33 | failureThreshold: 3 34 | resizePolicy: 35 | - resourceName: "cpu" 36 | restartPolicy: "NotRequired" 37 | resources: 38 | limits: 39 | cpu: '2' 40 | memory: 1Gi 41 | requests: 42 | cpu: 100m 43 | memory: 256Mi -------------------------------------------------------------------------------- /apps/java-app-resize/policy-v2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: Policy 3 | metadata: 4 | name: resize-pod-policy 5 | spec: 6 | rules: 7 | - name: resize-pod-policy 8 | match: 9 | any: 10 | - resources: 11 | kinds: 12 | - Pod 13 | preconditions: 14 | all: 15 | - key: "{{request.object.status.containerStatuses[0].ready}}" 16 | operator: Equals 17 | value: true 18 | mutate: 19 | patchStrategicMerge: 20 | spec: 21 | containers: 22 | - (name): sample-kotlin-spring 23 | resources: 24 | limits: 25 | cpu: 0.5 -------------------------------------------------------------------------------- /apps/java-app-resize/policy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: Policy 3 | metadata: 4 | name: policy-resize-pod 5 | annotations: 6 | argocd.argoproj.io/sync-wave: "2" 7 | spec: 8 | mutateExistingOnPolicyUpdate: true 9 | rules: 10 | - name: resize-pod 11 | match: 12 | any: 13 | - resources: 14 | kinds: 15 | - Pod 16 | context: 17 | - name: pod_name 18 | variable: 19 | jmesPath: "request.object.metadata.name" 20 | default: '' 21 | - name: pod_status 22 | variable: 23 | jmesPath: "request.object.status.containerStatuses[0].ready" 24 | default: false 25 | mutate: 26 | targets: 27 | - apiVersion: v1 28 | kind: Pod 29 | name: "{{pod_name}}" 30 | namespace: demo 31 | preconditions: 32 | all: 33 | - key: "{{target.status.containerStatuses[0].ready}}" 34 | operator: Equals 35 | value: true 36 | patchStrategicMerge: 37 | spec: 38 | containers: 39 | - (name): sample-kotlin-spring 40 | resources: 41 | limits: 42 | cpu: 0.5 -------------------------------------------------------------------------------- /apps/postgresql/database.yaml: -------------------------------------------------------------------------------- 1 | kind: DatabaseSecretEngineConfig 2 | apiVersion: redhatcop.redhat.io/v1alpha1 3 | metadata: 4 | name: postgresql-database-config 5 | annotations: 6 | argocd.argoproj.io/sync-wave: "3" 7 | argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true 8 | spec: 9 | allowedRoles: 10 | - postgresql-default-role 11 | authentication: 12 | path: kubernetes 13 | role: vault-admin 14 | connectionURL: 'postgresql://{{username}}:{{password}}@postgresql.default:5432?sslmode=disable' 15 | path: database 16 | pluginName: postgresql-database-plugin 17 | rootCredentials: 18 | passwordKey: postgres-password 19 | secret: 20 | name: postgresql 21 | username: postgres 22 | --- 23 | apiVersion: redhatcop.redhat.io/v1alpha1 24 | kind: DatabaseSecretEngineRole 25 | metadata: 26 | name: postgresql-default-role 27 | annotations: 28 | argocd.argoproj.io/sync-wave: "3" 29 | argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true 30 | spec: 31 | creationStatements: 32 | - CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}"; GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO "{{name}}"; 33 | maxTTL: 10m0s 34 | defaultTTL: 1m0s 35 | authentication: 36 | path: kubernetes 37 | role: vault-admin 38 | dBName: postgresql-database-config 39 | path: database -------------------------------------------------------------------------------- /apps/postgresql/policies.yaml: -------------------------------------------------------------------------------- 1 | kind: Policy 2 | apiVersion: redhatcop.redhat.io/v1alpha1 3 | metadata: 4 | name: database-creds-view 5 | annotations: 6 | argocd.argoproj.io/sync-wave: "3" 7 | argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true 8 | spec: 9 | authentication: 10 | path: kubernetes 11 | role: vault-admin 12 | policy: | 13 | path "database/creds/default" { 14 | capabilities = ["read"] 15 | } -------------------------------------------------------------------------------- /apps/postgresql/roles.yaml: -------------------------------------------------------------------------------- 1 | kind: KubernetesAuthEngineRole 2 | apiVersion: redhatcop.redhat.io/v1alpha1 3 | metadata: 4 | name: database-engine-creds-role 5 | annotations: 6 | argocd.argoproj.io/sync-wave: "3" 7 | argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true 8 | spec: 9 | authentication: 10 | path: kubernetes 11 | role: vault-admin 12 | path: kubernetes 13 | policies: 14 | - database-creds-view 15 | targetServiceAccounts: 16 | - default 17 | targetNamespaces: 18 | targetNamespaces: 19 | - default -------------------------------------------------------------------------------- /apps/postgresql/sample-app/config.yaml: -------------------------------------------------------------------------------- 1 | kind: ConfigMap 2 | apiVersion: v1 3 | metadata: 4 | name: sample-app-db-vault 5 | data: 6 | application.yml: | 7 | spring: 8 | application: 9 | name: sample-app-db-vault 10 | datasource: 11 | url: jdbc:postgresql://postgresql:5432/postgres 12 | cloud: 13 | vault: 14 | config.lifecycle: 15 | enabled: true 16 | min-renewal: 10s 17 | expiry-threshold: 30s 18 | kv.enabled: false 19 | uri: http://vault.vault:8200 20 | authentication: KUBERNETES 21 | postgresql: 22 | enabled: true 23 | role: postgresql-default-role 24 | backend: database 25 | kubernetes: 26 | role: database-engine-creds-role -------------------------------------------------------------------------------- /apps/postgresql/sample-app/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: sample-app-db-vault 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: sample-app-db-vault 9 | template: 10 | metadata: 11 | labels: 12 | app: sample-app-db-vault 13 | spec: 14 | containers: 15 | - name: sample-app-db-vault 16 | image: piomin/sample-app:1.0-gitops 17 | ports: 18 | - containerPort: 8080 19 | env: 20 | - name: SPRING_CONFIG_LOCATION 21 | value: /opt/config/application.yml 22 | volumeMounts: 23 | - mountPath: /opt/config 24 | name: config 25 | volumes: 26 | - name: config 27 | configMap: 28 | name: sample-app-db-vault 29 | serviceAccountName: default 30 | --- 31 | apiVersion: v1 32 | kind: Service 33 | metadata: 34 | name: sample-app-db-vault 35 | spec: 36 | type: ClusterIP 37 | selector: 38 | app: sample-app-db-vault 39 | ports: 40 | - port: 8080 -------------------------------------------------------------------------------- /apps/postgresql/schema.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: db.atlasgo.io/v1alpha1 2 | kind: AtlasSchema 3 | metadata: 4 | name: sample-spring-cloud-vault 5 | annotations: 6 | argocd.argoproj.io/sync-wave: "4" 7 | argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true 8 | spec: 9 | credentials: 10 | scheme: postgres 11 | host: postgresql.default 12 | user: postgres 13 | passwordFrom: 14 | secretKeyRef: 15 | key: postgres-password 16 | name: postgresql 17 | database: postgres 18 | port: 5432 19 | parameters: 20 | sslmode: disable 21 | schema: 22 | sql: | 23 | create table person ( 24 | id serial primary key, 25 | name varchar(255), 26 | gender varchar(255), 27 | age int, 28 | external_id int 29 | ); -------------------------------------------------------------------------------- /apps/vault/job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: vault-admin-initializer 5 | annotations: 6 | argocd.argoproj.io/sync-wave: "3" 7 | spec: 8 | template: 9 | spec: 10 | containers: 11 | - name: vault-admin-initializer 12 | image: hashicorp/vault:1.15.2 13 | env: 14 | - name: VAULT_ADDR 15 | value: http://vault.vault.svc:8200 16 | command: 17 | - /bin/sh 18 | - -c 19 | - | 20 | export VAULT_TOKEN=root 21 | sleep 10 22 | vault auth enable kubernetes 23 | vault secrets enable database 24 | vault write auth/kubernetes/config kubernetes_host=https://kubernetes.default.svc:443 kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt 25 | vault write auth/kubernetes/role/vault-admin bound_service_account_names=default bound_service_account_namespaces=default policies=vault-admin ttl=1h 26 | vault policy write vault-admin - <