diff --git a/config.yaml b/config.yaml index 1f29a53d5..a0b31cfb9 100644 --- a/config.yaml +++ b/config.yaml @@ -48,10 +48,7 @@ outputFormats: mediatype: application/json menus: - main: - - name: Workshop - url: https://play.validatedpatterns.io - weight: 25 + main: [] sitemap: changeFreq: monthly priority: 0.5 diff --git a/content/workshop/_index.adoc b/content/workshop/_index.adoc new file mode 100644 index 000000000..e458c6532 --- /dev/null +++ b/content/workshop/_index.adoc @@ -0,0 +1,23 @@ +--- +title: "Validated Patterns Workshop" +menu: + main: + weight: 25 + name: Workshop +--- + += Welcome to the Validated Patterns Workshop! + +== Overview + +**Reference Architectures with Added Value** + +Validated Patterns are an evolution of how you deploy and manage applications in a hybrid cloud. With a pattern, you can automatically deploy a full application stack through a GitOps-based framework. With this framework, you can create business-centric solutions while maintaining a level of Continuous Integration (CI) over your application. + +== About the Validated Patterns Workshop + +The goal of this workshop is to immerse the participant in foundational knowledge for creating, using and extending a GitOps pattern with the Validated Patterns framework. In this workshop participants will: + +- Learn GitOps fundamentals +- Deploy a Validated Pattern +- Extend an existing Validated Pattern diff --git a/content/workshop/additional-resources.adoc b/content/workshop/additional-resources.adoc new file mode 100644 index 000000000..9ffc6b82d --- /dev/null +++ b/content/workshop/additional-resources.adoc @@ -0,0 +1,9 @@ +--- +menu: workshop +title: Additional Resources +weight: 100 +--- + +include::modules/comm-attributes.adoc[] + += Appendix - Additional Resources diff --git a/content/workshop/cluster-domains.adoc b/content/workshop/cluster-domains.adoc new file mode 100644 index 000000000..ba5dfe8a3 --- /dev/null +++ b/content/workshop/cluster-domains.adoc @@ -0,0 +1,40 @@ +--- +menu: + workshop: + parent: Additional Resources +title: Cluster Domains +weight: 102 +--- + +:toc: +include::modules/comm-attributes.adoc[] + +[#limitations] +== Limitations + +By default ArgoCD does not expose any means to know the cluster's name/FQDN (Fully Qualified Domain Name) + +To make up for this the framework collects some cluster information in order to provide reusable parameters for cluster specific values. + +[#variables] +== Domain variables set by the pattern + +|=== +|**Variable**|**Purpose**|**Example (if applicable)** +| `global.clusterDomain` +| Domain of the cluster the application is running on without the `.apps` subdomain +|`mcg-hub.aws.validatedpatterns.io` +|`global.hubClusterDomain` +| The domain of the hub cluster **with the apps** subdomain - same as `localClusterDomain` on the hub. +| `apps.mcg-hub.aws.validatedpatterns.io` +| `global.localClusterDomain` +| The domain of the cluster the application is running on - **with the apps** subdomain +| `apps.mcg-hub.aws.validatedpatterns.io` +| `global.clusterPlatform` +| Name of the platform +| `aws`, `azure`, `gcp` +| `global.clusterVersion` +| OpenShift Cluster Version +| `4.16` + +|=== diff --git a/content/workshop/consuming-patterns.adoc b/content/workshop/consuming-patterns.adoc new file mode 100644 index 000000000..16946fc60 --- /dev/null +++ b/content/workshop/consuming-patterns.adoc @@ -0,0 +1,120 @@ +--- +menu: workshop +title: Validated Patterns +weight: 50 +--- + +:toc: +include::modules/comm-attributes.adoc[] +:imagesdir: /images/workshop + +== Consuming Validated Patterns +[#workflow] + +Let's dive into what happens when we deploy a pattern! Take a look at the following graphic to see what is deployed on the `hub` clusterGroup cluster. + +image::consuming-vpWorkflow-hub.png[Hub Cluster] + +1. The patterns operator deploys an instance of {rh-gitops} and creates the initial application +2. ArgoCD then deploys the operators, namespaces, projects and applications defined in the values files +3. If deploying across multiple clusters, the framework uses {rhacm} policies to create the instance of {rh-gitops} on the remote cluster + +NOTE: `values-hub.yaml` is the primary values file for the `hub` cluster + +image::consuming-vpWorkflow-managedCluster.png[Managed Cluster] + +1. When a managed cluster is joined to {rhacm} it needs to have a clusterGroup label applied to it for the correct policies and applications to be deployed. +2. {rhacm} will confirm the cluster is managed then deploy a rhacm agent +3. A placement policy is then deployed to the managed cluster which installs an instances of {rh-gitops} +4. The managed cluster's instance of ArgoCD will then deploy the resources declared in `values-clustergroup.yaml` + +IMPORTANT: Deploying the pattern through the OpenShift Console or via `pattern.sh` will deploy the patterns operator. + +== Element Layering +[#layering] + +Architecturally, Patterns share a very similar base structure, as seen below. + +image::consuming-common.png[common] + +Your specific industry or use-case is layered into the framework and utilizes the underlying base structure. In this example, the solution includes a bespoke application, cloud-native cluster storage, {rhacs}, and an enterprise container registry. The framework will deploy and configure these components through Helm charts, Kustomize manifests or Ansible if necessary. + +image::consuming-common-solution-layering.png[] + +What if we wanted to swap these products out with a similar or like-in-kind solution? The workflow remains the same: + +- remove the undesired application declaration +- declare the new application in your values file +- declare where the helm charts reside (path, repo) + +The following graphic illustrates swapping components in the framework: + +image::consuming-common-solution-layering-modular.png[] + +== The Base Architecture +[#skeleton] + +So where exactly are these different architectural components defined? + +=== GitOps + +link:https://github.com/validatedpatterns/patterns-operator[The {validated-patterns-op}] automatically deploys an instance of {rh-gitops-short} when a Pattern CR is installed on the cluster. + +Check out an example Pattern CR for an {mcg-pattern} installation: + +[source,yaml] +---- +apiVersion: gitops.hybrid-cloud-patterns.io/v1alpha1 +kind: Pattern +metadata: + name: multicloud-gitops + namespace: openshift-operators +spec: + clusterGroupName: hub + gitSpec: + targetRepo: https://github.com/validatedpatterns/multicloud-gitops.git + targetRevision: main +---- + +When the {validated-patterns-op} reconciles this CR, it creates an Argo Application using link:https://github.com/validatedpatterns/clustergroup-chart[the Validated Patterns Clustergroup Chart] and link:https://github.com/validatedpatterns/multicloud-gitops/blob/main/values-hub.yaml[the values-`<.spec.clusterGroupName>`.yaml values] from the CR above. + +=== Secrets Management + +The default secrets backend uses link:https://charts.validatedpatterns.io/charts/hashicorp-vault[Vault] and link:https://charts.validatedpatterns.io/charts/openshift-external-secrets[ESO]. + +As you can see in our link:https://github.com/validatedpatterns/multicloud-gitops/blob/main/values-hub.yaml[values-hub.yaml], we provide a namespace, subscription and an application for both of these components. + +When we run `./pattern.sh make install` it ultimately calls link:https://github.com/validatedpatterns/rhvp.cluster_utils/blob/main/playbooks/load_secrets.yml[the rhvp.cluster_utils.load_secrets playbook] and loads a secrets file from our home directory into Vault as part of the pattern installation process. + +=== ACM + +Similar to the secrets management, link:https://charts.validatedpatterns.io/charts/acm[ACM] is provided in `values-hub.yaml` as a namespace, subscription and an application. By providing a `managedClusterGroups` block in our `values-hub.yaml` we are able to provision spoke clusters with the Validated Patterns framework as well. + +[source,yaml] +---- +clusterGroup: + managedClusterGroups: + exampleRegion: + name: group-one + acmlabels: + - name: clusterGroup + value: group-one +---- + +Any cluster that ACM sees labeled with `clusterGroup=group-one` will be provisioned with our link:https://github.com/validatedpatterns/clustergroup-chart[Clustergroup chart] and linkhttps://github.com/validatedpatterns/multicloud-gitops/blob/main/values-group-one.yaml[values-group-one.yaml]. + +=== Imperative Jobs + +The example imperative job in the {mcg-pattern} is + +[source,yaml] +---- +clusterGroup: + imperative: + jobs: + - name: hello-world + playbook: rhvp.cluster_utils.hello_world + timeout: 234 +---- + +This gets transformed by link:https://github.com/validatedpatterns/clustergroup-chart/tree/main/templates/imperative[the Clustergroup chart] into several resources. This process is a bit complex and we will cover it in more detail later. diff --git a/content/workshop/creating-patterns.adoc b/content/workshop/creating-patterns.adoc new file mode 100644 index 000000000..efcccb79b --- /dev/null +++ b/content/workshop/creating-patterns.adoc @@ -0,0 +1,297 @@ +--- +menu: + workshop: + parent: Validated Patterns +title: Creating Patterns +weight: 52 +--- + +:toc: +include::modules/comm-attributes.adoc[] +:imagesdir: /images/workshop + +[IMPORTANT] +.Core Concepts +==== +* **Artifacts** Artifacts can include Helm Charts, Kustomize manifests, or plain Kubernetes manifests +* **Moving Artifacts into the Validated Patterns framework** Many artifacts require conversion to Helm chart templates and parameterizing certain values +* **Helm** Helm is a kubernetes package manager that allows you to define, install and manage kubernetes applications as reusable packages called charts +==== + +[#creation] +== Creation Process + +image::create-where2start.png[] + +One of the first things we should do before embarking on creating a pattern is to identify a problem to solve. +This can come from an existing customer problem or an idea on how we can showcase some of our Red Hat products working together. +You can use one of our simplest validated patterns, multicloud gitops, as a starting point by using the template to create a new Git repository in your local GitHub account. +You might already have an existing demo that you can use and convert it to use the Validated Pattern framework. + +The next step is to identify the technology components that you will need. +In the case of an existing demo you might already know which components are part of the pattern. + +The final step is to ensure that the configuration of those components, and the integration points, are handled in the pattern. + + +== Setting up naming + +There are two names that exist a pattern: The `pattern` name and the `clusterGroup` name. +Best practice is to align `pattern` with the repository title as it drives file naming for secrets file: `values-secrets-{pattern}.yaml` + +clusterGroup is most relevant when you have multiple clusters. Otherwise you can rely on the default `hub` group. + +[source,yaml] +---- + global: + pattern: multicloud-gitops + options: + useCSV: false + syncPolicy: Automatic + installPlanApproval: Automatic + main: + clusterGroupName: hub +---- + +== Adding to the Bill of materials + +There are three major resources you can add: Operators, Applications (helm charts) and ansible (imperative) scripts. + + +== Introducing new operators + +Operators can be simply added to the clusterGroup as subscriptions + +[source,yaml] +---- + subscriptions: + acm: + name: advanced-cluster-management + namespace: open-cluster-management + channel: release-2.9 + s3: + name: ack-s3-controller + namespace: ack-system + channel: alpha + source: community-operators + cte: + name: cte-k8s-operator + namespace: openshift-operators + channel: stable + source: certified-operators +---- + +To find the *correct* names the easiest way to do this is to jump into the console, find the operator, get to the install screen + +image::operator-install.png[] + +The URL at this point can be used to pull out the name (pkg), catalog, channel etc. + + +== Introducing new helm charts + +The easiest way to start with new helm charts is to copy and paste from the example charts in multicloud-gitops. +Each helm chart gets added as an argoCD application: + +[source,yaml] +---- + applications: + golang-external-secrets: + name: golang-external-secrets + namespace: golang-external-secrets + project: hub + path: common/golang-external-secrets + + cnv: # arbitrary + name: cnv # name of argoCD applcation + namespace: openshift-cnv + project: cnv # argoCD project + path: charts/all/cnv # relative path to the chart +---- + +Convention puts the helm charts for the validated-pattern under `charts/`. + +== Chaining configuration into the charts. + +One key element of validated patterns is a structured set of variable overrides. +These overrides are key to allowing the template to be kept as 'DRY' as possible when configuring. +For this context it's important to understand the evaluation order: + +1. The default values defined in the chart (or sub charts). +2. `values-global.yaml` file +3. Values within clusterGroup files (e.g. `values-hub.yaml) +4. Values override files with precedence for the (top/bottom) of the valid list +5. Values overrides shown in individual application definitions + + + +*Default files installed in a chart* + +All helm charts *MUST* contain a `values.yaml` file. +Values that are defined here should be considered the 'base' consideration. +While it's possible to not define a value here, yet use it in the chart, it is strongly recommended sensible defaults are included. + + +*`values-global.yaml` file* + +Values here are applied to every argocd application as it is picked up directly by the validated patterns operator. +The values here are also usable in the `clusterGroup` files + + +*ClusterGroup* files overrides + +Values can directly be used within the context of files such as `values-hub.yaml` + +[source,yaml] +---- + letsencrypt: + region: '' + server: https://acme-v02.api.letsencrypt.org/directory + # staging URL + # server: https://acme-staging-v02.api.letsencrypt.org/directory + email: chris@thebutlers.me + clusterGroup: + name: hub + isHubCluster: true + +---- + + +*Values override files* + +`clusterGroup`s define an override file list which is evaluated in order. +The cluster overrides files can take of the variables defined in `values-global.yaml` as well as metadata assembled by the operator, OpenShift and {rhacm}. + +These dynamic variables allow abstraction from variability between OpenShift installation. `storageClasses` varying across `clusterPlatform` is a clear example. + +The order of operations can be changed based on uses requirements easily with the list. + +[source,yaml] +---- + overrides: + - '/overrides/values-{{ $.Values.global.clusterPlatform }}.yaml' + - '/overrides/values-{{ $.Values.global.clusterPlatform }}-{{ $.Values.global.clusterVersion }}.yaml' + - '/overrides/values-{{ $.Values.global.clusterPlatform }}-{{ $.Values.clusterGroup.name }}.yaml' + - '/overrides/values-{{ $.Values.global.clusterVersion }}-{{ $.Values.clusterGroup.name }}.yaml" +---- + +*Discovery activity*: Logging into console for the `cluster` OpenShift gitops. +Examine the root application to discover what variables have been provided to ArgoCD. + + +*Individual application overrides* + +Individual applications should be reserved for use-cases where there is risk of conflict across multiple charts in the same application group. +They should be viewed as absolutely hard coded into the pattern. + + +[source,yaml] +---- + applications: + coffeeshop-test: + name: quarkuscoffeeshop-demo + namespace: quarkuscoffeeshop-demo + project: quarkuscoffeeshop-demo + path: charts/store/quarkuscoffeeshop-charts + overrides: + - name: ocp_auth.bind_dn + value: "uid=ldap_admin\\,cn=users\\,cn=accounts\\,dc=redhatlabs\\,dc=dev" + - name: ocp_auth.bind_password + value: "supersecret" +---- + +== Using Patternizer to create new patterns + +When creating new patterns you have two options: you can either fork an existing pattern and modify it to suit your purpose or start a new pattern from scratch. + +link:https://github.com/validatedpatterns/patternizer[The patternizer tool] exists to expedite starting a pattern from scratch. + +To create a new pattern you merely need to run `podman run --pull=newer -v "$PWD:$PWD:z" -w "$PWD" quay.io/validatedpatterns/patternizer init --with-secrets` (you can omit the `--with-secrets` if you don't need to use the secrets framework.) + +On an empty directory, named `workshop-pattern`, this currently produces the following files: + +[source,bash] +---- +workshop-pattern +├── ansible.cfg # the default ansible configuration +├── Makefile # a stub Makefile which includes Makefile-common +├── Makefile-common # where all the common commands (install, load-secrets, etc) live +├── pattern.sh # the convenvience utility container (has oc, helm, makefile, etc) +├── values-global.yaml # names the pattern based on the directory and sets common defaults +├── values-prod.yaml # contains the components for using the secrets framework +└── values-secret.yaml.template # a stub for the secrets file +---- + +The `values-global.yaml` contains + +[source,yaml] +---- +global: + pattern: workshop-pattern + singleArgoCD: true + secretLoader: + disabled: false +main: + clusterGroupName: prod + multiSourceConfig: + enabled: true + clusterGroupChartVersion: 0.9.* +---- + +and the `values-prod.yaml` contains + +[source,yaml] +---- +clusterGroup: + name: prod + namespaces: + - workshop-pattern + - vault + - external-secrets-operator: + operatorGroup: true + targetNamespaces: [] + - external-secrets + subscriptions: + eso: + name: openshift-external-secrets-operator + namespace: external-secrets-operator + channel: stable-v1 + applications: + openshift-external-secrets: + name: openshift-external-secrets + namespace: external-secrets + chart: openshift-external-secrets + chartVersion: 0.0.* + vault: + name: vault + namespace: vault + chart: hashicorp-vault + chartVersion: 0.1.* +---- + +[TIP] +.Shell Function +==== +You can add a simple shell function to your shell config file (ex `~/.zshrc`) to enable easier use of the tool. + +[source,bash] +---- +pattern() { + podman run --pull=newer \ + -v "$PWD:$PWD:z" \ + -w "$PWD" \ + quay.io/validatedpatterns/patternizer "$@" +} +---- + +Now you can more simply run `pattern init`. +==== + +[NOTE] +.Idempotency +==== +The `pattern init` command is idempotent and can be used several times during pattern creation to update the patterns values files. +You can go from just using `pattern init` to `pattern init --with-secrets` to add the secret framework to your pattern. +If you use `helm create` (or `./pattern.sh helm create`) to create helm charts and then run `pattern init` again, the helm charts +will be automatically added to your `values-prod.yaml`. +==== diff --git a/content/workshop/demo.md b/content/workshop/demo.md new file mode 100644 index 000000000..7c8846ead --- /dev/null +++ b/content/workshop/demo.md @@ -0,0 +1,13 @@ +--- +menu: + workshop: + parent: Introduction +title: Demo +weight: 21 +--- + +# Validated Patterns Workshop Demo + +This interactive demo walks through installing the Validated Patterns operator and deploying the [Multicloud Gitops](/patterns/multicloud-gitops/) pattern on a fresh OpenShift cluster using the Validated Patterns Catalog. + +{{< arcade url="https://demo.arcade.software/share/m8RRb4Wanooo2Vq6iV5O" title="Validated Patterns Workshop Demo" >}} diff --git a/content/workshop/eso-and-vault.adoc b/content/workshop/eso-and-vault.adoc new file mode 100644 index 000000000..44121f3d6 --- /dev/null +++ b/content/workshop/eso-and-vault.adoc @@ -0,0 +1,153 @@ +--- +menu: + workshop: + parent: Secrets Management +title: ESO and Vault +weight: 61 +--- + +:toc: +include::modules/comm-attributes.adoc[] +:imagesdir: /images/workshop + +[#esoVault] +== ESO and Vault + +IMPORTANT: The Validated Patterns team settled on using **references to secrets** + +We currently implement GitOps + Secrets via link:https://external-secrets.io/v0.7.0/[External Secrets Operator] and link:https://www.vaultproject.io/[HashiCorp Vault] + +The main reasons for this choice at the time were the following: + +* Sealed Secret approach carries additional risks +* ESO allows us a certain level of independence from the Secrets Management System +* HashiCorp Vault is currently the most popular Secret Management Systems +* Advised by our security team +* CyberArk leaders (masters) cannot be hosted on OpenShift +* Secret injection is incompatible with Pods owned by Operators + + +[#approaches] +== Approaches + +There are two fundamental approaches to manage secret material within a GitOps context: + +* **Encrypted Secrets** stored inside Git repositories +* **References to Secrets** stored inside Git repositories and the actual secret is stored somewhere else + +[#encryptedSecrets] +=== Encrypted Secrets + +* Secrets are stored in an encrypted form inside the Git repository +* Automation and helper tools do the decryption to create Kubernetes secrets from them +* A number of projects exist that implement this approach: +** link:https://github.com/bitnami-labs/sealed-secrets[Sealed Secrets] +** link:https://github.com/mozilla/sops[Mozilla's Secret OPerationS (SOPS)] +** Other smaller ones (link:https://github.com/kapicorp/tesoro[Tesoro], link:https://github.com/Soluto/kamus[Kamus]) + +These projects provide an easy way to encrypt and decrypt the secrets, and make sure that only authorized users can access them. +The encryption method and key management should be considered carefully as they are important to ensure the security of the secrets. + +IMPORTANT: You should evaluate and choose the right project that fits your specific use case and requirements. + +[#secretReferences] +=== References to Secrets + +This approach requires two main parts: +* A Secret Management System (link:https://www.vaultproject.io/[HashiCorp Vault], link:https://www.conjur.org/[Conjur], link:https://lyft.github.io/confidant/[Confidant]) +* A controller that retrieves the secret from the Secret Management System and translates it to a Kubernetes Secret or injects it into the Pod directly (link:https://external-secrets.io/v0.7.0/[External Secrets Operator], link:https://github.com/kubernetes-sigs/secrets-store-csi-driver[Kubernetes Secrets Store CSI Driver], link:https://developer.hashicorp.com/vault/docs/platform/k8s/injector[Vault Agent Injector]) +* Secrets are uploaded/created directly into the Secret Management System +* References to secrets are stored in Git +* A controller reads the references to secrets and translates them into Kubernetes secrets + +IMPORTANT: HashiCorp Vault runs only on the HUB + +[#vault] +* The Vault has multiple kubernetes backends configured by the unsealVault Cron Job (one for each cluster) +* There is one ESO instance for each cluster (ESO is currently unsupported) +* Each ESO instance logs into the HashiCorp Vault using a specific login path that was configured with the credentials of that instance +* Vault will authenticate the requests by talking to the API endpoints of the clusters where the login request originated from + +[#eso] + +* At a high-level the External Secrets Operator reads secrets from the Vault and converts them into Kubernetes Secrets +* To populate the Vault with our secret material we use `make load-secrets` +* Normally a user would create ~/values-secret-.yaml following the contents of values-secret.yaml.template in the pattern's git repository +* Running `./pattern.sh make load-secrets` will populate the Vault with our secrets + +NOTE: Provisioning the vault can be complex, so it is taken care of by the framework + +* Vault unsealing and configuration happens in the **imperative** namespace with a CronJob + +NOTE: The Unseal vault cron job is idempotent and runs every five minutes on the hub only + +image::unseal-vault-cronjob.png[] + +* The **vaultkeys** secret containing the keys to unseal the vault and the vault root token that can be used to login to the vault's UI is created + +IMPORTANT: It is **strongly** recommended that the **vaultkeys** secret in the **imperative** namespace is exported to a safe space and removed. It contains the keys needed to unseal the vault whenever it gets restarted + +[#vaultconcepts] +=== Vault Concepts + +* Secrets are stored in **Vault** in specific **paths** +* **secret/global/config-demo** is the **path** +* Each **path** can have multiple attributes +* **secret** is one attribute at this path +* In this case secret is autogenerated by default inside the vault directly + +[.INFORMATION] +==== +By default there are three paths that can be used + +* **secret/global/*** accessible by the hub and all managed clusters +* **secret/hub/*** accessible only by the hub cluster +* **secret//*** accessible only by the fqdn-defined cluster +==== + +TIP: You can think of a path as being a single dictionary with multiple keys. An external secret looks for a path and then has a templating system to extract keys and put them in kubernetes secrets. + +[source,yaml] +---- +secrets: + - name: config-demo + vaultPrefixes: + - global + fields: + - name: secret + onMissingValue: generate + vaultPolicy: validatedPatternDefaultPolicy +---- + +From the vault interface perspective: + +image::global-configdemo-vault.png[] + +[#esoconcepts] +=== ESO Concepts + +ESO will read secrets from vault paths (e.g. "`secret/data/global/config-demo`" is a path) + +This ExternalSecret object will read all the attributes of the vault key `secret/global/config-demo` and will create a secret called `config-demo-secret` with one entry per attribute. + +[source,yaml] +---- +apiVersion: "external-secrets.io/v1beta1" +kind: ExternalSecret +metadata: + name: config-demo-secret + namespace: config-demo +spec: + secretStoreRef: + name: vault-backend + kind: ClusterSecretStore + target: + name: config-demo-secret + template: + type: Opaque + dataFrom: + - extract: + key: secret/data/global/config-demo +---- + +NOTE: How secret material ends up in the vault at a specific path is explained in detail in an upcoming section diff --git a/content/workshop/getting-started.adoc b/content/workshop/getting-started.adoc new file mode 100644 index 000000000..6fce58c8b --- /dev/null +++ b/content/workshop/getting-started.adoc @@ -0,0 +1,51 @@ +--- +menu: workshop +title: Getting Started +weight: 10 +--- + +:toc: +include::modules/comm-attributes.adoc[] + +== Prerequisites + +The following CLI tools are required for running the exercises in this workshop. Please have them installed and configured prior to beginning any of the workshop chapters. + +[cols="4*^,4*.",options="header,+attributes"] +|=== +|**Tool**|**macOS**|**Fedora**|**Windows** + +|`Git` +| https://git-scm.com/download/mac[Download] +| https://git-scm.com/download/linux[Download] +| https://git-scm.com/download/win[Download] + +|`Podman` +| https://podman.io/docs/installation#macos[Download] +| `dnf install podman` +| https://github.com/containers/podman/blob/main/docs/tutorials/podman-for-windows.md[Install] + +|=== + +[#openshift] +== Setup OpenShift + +To run OpenShift4, you need to have one provisioned using https://try.openshift.com[try.openshift.com] or can use any existing OpenShift4 cluster. +Once you have your cluster, you can download the latest OpenShift client(oc) from https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest/[here] and add to your path. + +You can check the OpenShift version using: + +[source,bash] +---- +oc version +---- + +The output should show oc version >=4.18: + +[source,bash] +---- +Client Version: 4.21.17 +Kustomize Version: v5.7.1 +Server Version: 4.21.8 +Kubernetes Version: v1.34.5 +---- diff --git a/content/workshop/gitops-rough-edges.adoc b/content/workshop/gitops-rough-edges.adoc new file mode 100644 index 000000000..256f64be5 --- /dev/null +++ b/content/workshop/gitops-rough-edges.adoc @@ -0,0 +1,87 @@ +--- +menu: workshop +title: GitOps Rough Edges +weight: 80 +--- + +:toc: +include::modules/comm-attributes.adoc[] +:imagesdir: /images/workshop + + +[#declarativeSync] +== Declarative Synchronization + +Making the transition to GitOps we're often faced with the challenge of implementing applications and configurations that weren't built with declarative deployments, more specifically, GitOps in mind + +We have come across multiple use-cases where we need to apply imperative actions against a cluster to either get information or apply a change (e.g. node labeling for {rhodf} ) + +We needed a way to declaratively define those **do-wait-do** processes + +[#imperative] + +It is also important to understand how imperative changes fit into declarative deployments + +[.Important] +==== +- WHAT you are doing imperatively won't change, it become more about HOW you are applying that change to the cluster +- It's important to not let the way you solve these problems become your default approach for application deployments. +==== + +We have options! + +|=== +|**Resource Type**|**Explanation** +| `kubernetes Jobs` +| A job creates one or more pods and will continue to retry execution of the pods until a specified number of them successfully terminate +|`kubernetes cronJobs` +| A cronJob creates jobs on a repeating schedule and uses the cron job syntax +|`Sync-Phases` +| ArgoCD Software Agents that automatically pull the desired state declarations from the source +|'Sync-Waves' +| ArgoCD Software Agents that continuously observe actual system state and attempt to apply the desired state + +|=== + +[#jobs] +=== Jobs + +NOTE: Jobs are part of the kubernetes batch api group + +Within the Patterns framework we mainly use jobs to: + +* Wait for dependent resources to become available, giving control over the deployment rollout +* Configure applications that rely heavily on API or console interaction (e.g. Quay Enterprise) +* Interrogate clusters resources such as secrets generated by resources out of scope of the pattern + +[#cron] +=== cronJobs + +NOTE: cronJobs are part of the kubernetes batch api group + +Within the Patterns framework we mainly use jobs to: + +* schedule imperative tasks in the imperative framework such as keeping the Vault unsealed +* run Ansible playbooks + +IMPORTANT: Ansible Roles and Playbooks **MUST** be idempotent to prevent sync failures + +NOTE: The cronJob syntax is the same as in posix systems + +[source,bash] +---- +# ┌───────────── minute (0 - 59) +# │ ┌───────────── hour (0 - 23) +# │ │ ┌───────────── day of the month (1 - 31) +# │ │ │ ┌───────────── month (1 - 12) +# │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday; +# │ │ │ │ │ 7 is also Sunday on some systems) +# │ │ │ │ │ OR sun, mon, tue, wed, thu, fri, sat +# │ │ │ │ │ +# * * * * * +---- + +Check out these links for more information about link:https://kubernetes.io/docs/concepts/workloads/controllers/job/[Kubernetes Jobs] and link:https://kubernetes.io/docs/concepts/workloads/controllers/cronjobs/[Kubernetes Cron Jobs] + +[#christian] +Check out link:https://developers.redhat.com/e-books/path-gitops[The Path to GitOps] by Christian Hernandez! diff --git a/content/workshop/imperative.adoc b/content/workshop/imperative.adoc new file mode 100644 index 000000000..b9ff10096 --- /dev/null +++ b/content/workshop/imperative.adoc @@ -0,0 +1,75 @@ +--- +menu: + workshop: + parent: Additional Resources +title: Imperative Framework +weight: 101 +--- + +:toc: +include::modules/comm-attributes.adoc[] + +[#versus] +== Imperative vs. Declarative Actions + +What's the difference between Imperative and Declarative actions? + +Declarative is when we **describe** what we want in our clusters, and we let the GitOps controller reconcile the difference between what's in Git and what's in the cluster. + +Imperative is where we take action on the cluster. A simplified way of thinking about this is all of the actions done by hand (e.g. `oc apply -f`, `kubectl`) + + +During the development of the imperative framework there as a realization that there was a generalized need to support a limited mechanism for running these imperative actions in the most declarative way possible. + +We needed an automated, self-contained and supportable mechanism to interact with elements of OpenShift that could not be predicted prior to installation. + +Some examples of when this is necessary: + +* The management of secrets across clusters - the need to make clusters mutually trust each others' Certificate Authorities and transfer of tokens/credentials + + +[#declaring] +== Defining an imperative action + +NOTE: The following code snippet is from the `values-hub.yaml` in the multicloud-gitops pattern + +[source,yaml] +---- + imperative: + # The default schedule is every 10 minutes: imperative.schedule + # Total timeout of all jobs is 1h: imperative.activeDeadlineSeconds + # imagePullPolicy is set to always: imperative.imagePullPolicy + jobs: + - name: hello-world + # ansible playbook to be run + playbook: common/ansible/playbooks/hello-world/hello-world.yaml + # per playbook timeout in seconds + timeout: 234 + # verbosity: "-v" +---- + + +When the pattern is deployed a cronJob is created in the `imperative` namespace. By default it is set to run `every 10 minutes`. + +The cronJob (see below) has been parameterized so we can change the intervals and define our imperative playbooks. + +[#cron-example] + +[source,yaml] +---- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{ $.Values.clusterGroup.imperative.cronJobName }} + namespace: {{ $.Values.clusterGroup.imperative.namespace}} +spec: + schedule: {{ $.Values.clusterGroup.imperative.schedule | quote }} + # if previous Job is still running, skip execution of a new Job + concurrencyPolicy: Forbid + jobTemplate: + spec: + activeDeadlineSeconds: {{ $.Values.clusterGroup.imperative.activeDeadlineSeconds }} + template: + metadata: + name: {{ $.Values.clusterGroup.imperative.jobName }} +---- diff --git a/content/workshop/introduction.adoc b/content/workshop/introduction.adoc new file mode 100644 index 000000000..b83668726 --- /dev/null +++ b/content/workshop/introduction.adoc @@ -0,0 +1,74 @@ +--- +menu: workshop +title: Introduction +weight: 20 +--- + +:toc: +include::modules/comm-attributes.adoc[] +:imagesdir: /images/workshop + +[#what] + +== What are Validated Patterns? + +Validated Patterns are an evolution of reference architectures. Each pattern is given a lifecycle when it is added to Red Hat's continuous integration (CI) system because we're testing against the latest version of operators, OpenShift releases and across multiple public cloud environments. + +image::Linux-Penguin-Evolution-1.png[evolution] + +[#goals] + +== Business Goals + +Every organization is facing the need to transform itself into a digital business. It's part of an evolution to help **accelerate innovation** by making application development faster - fueled with data insights, and enabled through technologies like containers, kubernetes and artificial intelligence/machine learning. + +In order to be successful, they know they must also look at how to be more **operationally agile** and **efficient** in order to quickly meet business needs. Hybrid-Cloud computing coupled with these new technologies is helping create new opportunities to achieve these goals. + +Validated Patterns are focused on customer solutions and Proof-Of-Concepts (POC's). + +image::introduction-goals.png[] + +[#benefits] + +== Benefits + +Red Hat's validated patterns are a collection of repositories that show a customers use case in the form of Kubernetes Resources (helm charts, kustomize, primitive objects) that describe an hybrid cloud stack declaratively and comprehensively; from its services down to the supporting infrastructure. Validated Patterns facilitate complex, highly reproducible deployments and are ideal for operating these deployments at scale using GitOps operational practices. + +image::introduction-pattern-benefits.png[] + +* Use a GitOps model to deliver the use case as code +* Use as a POC, modified to fit a particular need that you can evolve into a real deployment. +* Highly reproducible - great for operating at scale +* Open for collaboration, so anyone can suggest improvements, contribute to them or use them as the Git Repositories are all upstream +* Can be modified for customers needs. If you would like to swap out a component, use Ceph Storage instead of S3, it is as easy as commenting out sections and including other repos. +* Tested. Once made a validated pattern, the use case is included into Red Hat's CI and continues to be tested across product versions while the pattern remains active. + +[#batteries] + +== Batteries Included + +Validated Patterns are a "batteries included" solution. This means that wherever you start with the patterns framework a core and configurable set of components are delivered out of the box. + +Take a look at the following graphic - each Validated Pattern (use-case dependent) consumes these technologies. The modularity of the framework allows us to add/remove resources to meet the requirements of the use-case. If a pattern only deploys on a single cluster, there is no need to deploy {rhacm}, therefore we can remove the declarations for {rhacm} in the `values-hub.yaml`. + +image::introduction-techSlide.png[] + +IMPORTANT: Reusability is a major theme that will be seen throughout this workshop. + +When we dive into these core technologies we start to see the low-level technology being used throughout the framework + +image::introduction-batteriesIncluded.png[] + +[#whotheyfor] + +== Who are patterns for? + +Value provided by Validated Patterns is unique to each individual or organization. Generally speaking Patterns provide value to our: + +image::introduction-pattern-values.png[] + +* Customers - get going faster, reduce risk, time to value +* Sales - don't spend time architecting, they have a tested use case for a base deployment. Though messaged for a specific vertical in some cases, they can be used across other verticals. +* Partners - free use of a defined architecture that you can build a solution on top of. All the value to accelerate sales/deployments. +** Can also be used as a way to promote your product (HashiCorp) +* GSIs or Consulting Services - predefined architectures, ready to deploy with minimal modifications. They can trust in the use-case as it is maintained across production version updates. diff --git a/content/workshop/landscape.adoc b/content/workshop/landscape.adoc new file mode 100644 index 000000000..661755c5c --- /dev/null +++ b/content/workshop/landscape.adoc @@ -0,0 +1,30 @@ +--- +menu: workshop +title: Pattern Landscape +weight: 30 +--- + +:toc: +include::modules/comm-attributes.adoc[] +:imagesdir: /images/workshop + +== Available Validated Patterns + +[#patterns] + +The number of available Patterns has significantly increased with more involvement from the field and our partners. Take a look at the following graphic - at the beginning of 2024, we had a few Patterns delivered by our Partners and few more from the Patterns team and field. + +image::landscape-early24.png[] + +As of June 2026, we can see how far the project has come! + +image::landscape-q2cy26.png[width=960] + +== Patterns Library Links + +The easiest and fastest way to find out what Validated Patterns are available is to check out the link:/patterns[Patterns tab on this site.] + +[#github] + +Each pattern will have a link to its source code but you can also browse our link:https://github.com/orgs/validatedpatterns/repositories[Github org] directly. +Additional patterns may be available in the link:https://github.com/orgs/validatedpatterns-sandbox/repositories[sandbox org]. diff --git a/content/workshop/multiple-clusters.adoc b/content/workshop/multiple-clusters.adoc new file mode 100644 index 000000000..68f4669ac --- /dev/null +++ b/content/workshop/multiple-clusters.adoc @@ -0,0 +1,65 @@ +--- +menu: workshop +title: Multiple Clusters +weight: 90 +--- + +:toc: +include::modules/comm-attributes.adoc[] +:imagesdir: /images/workshop + +[#why] +== Why manage multiple clusters? + +=== Kubernetes adoption leads to multicluster +[.INFORMATION] +==== +"As Kubernetes gains adoption across the industry, scenarios are arising in which I&O teams are finding they must deploy and manage multiple clusters, either in a single region on-premises or in the cloud, or across multiple regions....for a number of reasons, including multi-tenancy, disaster recovery, and with hybrid, multicloud, or edge deployments." +==== + +[#challenges] +== Multi-Cluster Challenges + +image::multicluster-challenges.png[] + +* This increase of clusters presents challenges to organizations responsible for managing these clusters. +* The infrastructure/operations teams that are responsible for ensuring the deployment, configuration and management of these clusters will need the proper tools. +* The DevOps teams will need clusters to develop their applications and will have increasing requests for more resources and consistency across clusters. +* The Organization as a whole will need to have multiple target environments in which to deploy clusters. +* In high volume scenarios the clusters might need to be created in cloud providers depending on cost and resource availability. +* This presents a challenge where a central management tool is needed to ensure that operators can deploy, maintain and manage resources. + + +[#options] +== Multi-Cluster Management Options + +* You can do it manually using custom scripts +** This presents many challenges as scripts are homegrown and maintainability can become an issue. +* You can use ArgoCD to a degree +** ArgoCD's forte is to declaratively deploy applications onto a Kubernetes cluster not manage clusters. +* Use {rhacm} which is built to manage multiple clusters + +[#rhacm] +=== What is {rhacm} + +Red Hat Advanced Cluster Management for Kubernetes controls clusters and applications from a single console, with built-in security policies. Extend the value of Red Hat OpenShift by deploying apps, managing multiple clusters, and enforcing policies across multiple clusters at scale. Red Hat's solution ensures compliance, monitors usage, and maintains consistency. + +For more information visit: link:https://www.redhat.com/en/technologies/management/advanced-cluster-management[Advanced Cluster Management] + +image::multicluster-rhacm-value.png[] + +* Multi-Cluster Lifecycle Environment: +** You can centrally create, update and delete Kubernetes clusters across multiple private and public clouds +** Search, find and modify any kubernetes resource across the entire domain. +** Quickly troubleshoot and resolve issues across your federated domain + +* Policy Driven Governance +** Centrally set & enforce policies for security, applications, & infrastructure +** Quickly visualize detailed auditing on configuration of apps and clusters +** Built-in CIS compliance policies and audit checks +** Immediate visibility into your compliance posture based on your defined standards + +* Advanced Application Lifecycle +** Easily Deploy Applications at Scale +** Deploy Applications from Multiple Sources +** Quickly visualize application relationships across clusters and those that span clusters diff --git a/content/workshop/multisource.adoc b/content/workshop/multisource.adoc new file mode 100644 index 000000000..7aa37b8c3 --- /dev/null +++ b/content/workshop/multisource.adoc @@ -0,0 +1,144 @@ +--- +menu: + workshop: + parent: Additional Resources +title: Multisource Patterns +weight: 103 +--- + +:toc: +include::modules/comm-attributes.adoc[] +:imagesdir: /images/workshop + +[#meaning] +== What is MultiSource? + +Using multiple-sources in our ArgoCD applications gives the Patterns framework a lot of flexibility in how applications are deployed. In the code snippet below we can see that we have a chart called `acm` where we have our normal values getting assigned, but also using values from another repository - in this case `https://github.com/f00/multicloud-gitops` + +[source,yaml] +---- +# MULTISOURCE +sources: +- ref: patternref + repoURL: https://github.com/foo/multicloud-gitops + targetRevision: test2 +- chart: acm + repoURL: https://charts.validatedpatterns.io/ # default + targetRevision: 0.1.* + helm: + ignoreMissingValueFiles: true + valueFiles: + - $patternref/values-global.yaml + - $patternref/values-hub.yaml + - $patternref/values-AWS.yaml + - $patternref/values-AWS-4.13.yaml + - $patternref/values-AWS-hub.yaml + - $patternref/values-4.13-hub.yaml + parameters: + ... +---- + +With a single source we can only apply values files from within the repository, or declare them within the `applications` key. The primary issue with this approach is readability and reusability. The values file grows to a point where it is hard to follow the changes being made. + +[source,yaml] +---- +# SINGLE SOURCE +source: + repoURL: https://github.com/foo/multicloud-gitops + path: common/acm + targetRevision: test2 + helm: + valueFiles: + - /values-global.yaml + - /values-hub.yaml + - /values-AWS.yaml + - /values-AWS-4.13.yaml + - /values-AWS-hub.yaml + - /values-4.13-hub.yaml + parameters: + ... +---- + +[#how] + +== How it works + +IMPORTANT: Multi-source needs to be enabled for the clusterGroup helm chart via: `main.multiSourceConfig.enabled` + +Other applications stay `as-is` (e.g. single source like they have always been) + +Switching an application to use the Patterns chart and multisource capability requires a small tweak in the `values-*.yaml` + +NOTE: Yes, you can change the default VP repository + +Here's what it would look like: + +**Original Configuration:** +[source,yaml] +---- +acm: + name: acm + path: common/acm +---- + +**Updated Configuration:** +[source,yaml] +---- +acm: + name: acm + chart: acm + chartVersion: 0.0.* + # Tweak the default below to point to an alternative chart repo (OCI registries are + # also supported) + # repoURL: https://charts.validatedpatterns.io +---- + +[#status] +== Status + +* link:https://github.com/validatedpatterns/patterns-operator/pull/85[Pattern Operator PRs merged] +* link:https://github.com/validatedpatterns/common/pull/350[Common repository PRs merged] +* link:https://charts.validatedpatterns.io[VP Helm Chart Repository] set up and populated +* Industrial Edge has all VP charts as multisource (clusterGroup, ACM, ESO, Vault) +* MultiCloud GitOps has the clusterGroup chart as multisource enabled + +[#ux] + +== User Experience + +image::multisource-config.png[] + +[#helmRepo] +== Helm Chart Repository + +[.INFORMATION] +.How does it work? +==== +* `common/` now has a github workflow which is triggered at every commit to main **chart-branches.yaml** +* If any commit changes one of the charts (except `operator-install`) the corresponding branch in common for that chart will be updated (`git subtree split`) +* This makes it so that the **link:https://github.com/validatedpatterns/common/tree/hashicorp-vault-main-single-chart[hashicorp-vault-main-single-chart]** branch only contains the code for the hashicorp-vault chart +* Then it takes that hashicorp-vault-main-single-chart branch and pushes it out to the `validatedpatterns/hashicorp-vault` git repository +==== + +== TL;DR + +**validatedpatterns/{acm,clustergroup,hashicorp-vault...}** repos are automatically kept up to date every time we push to common + +[#chart-release] + +== Creating a chart release + +1. Change the version in `common/(chart}/Chart.yaml` (e.g. acm ver 0.0.2) +2. Make sure the version propagated correctly in the vp/acm-chart repository +3. From within the acm-chart repository, tag and push the new version `v0.0.2` +4. In `validatedpatterns/helm-charts`, a github workflow is triggered that will: +** build the helm chart +** upload it in the assets of the helm-charts repository +** update index.yaml + +[#next] +== Next Steps + +* Continue to monitor for any issues in Argo +* Expand the usage in other patterns +* Create a simple way to test charts without pushing them to the main repo (dev/qa workflow) diff --git a/content/workshop/partners.adoc b/content/workshop/partners.adoc new file mode 100644 index 000000000..6aa3e2e4c --- /dev/null +++ b/content/workshop/partners.adoc @@ -0,0 +1,38 @@ +--- +menu: + workshop: + parent: Pattern Landscape +title: Partners +weight: 31 +--- + +:toc: +include::modules/comm-attributes.adoc[] +:imagesdir: /images/workshop + +== Working with Partners + +Partners have taken notice of what Validated Patterns can bring to their organizations. In turn, they have had focused development cycles to deploy their product/features using the Validated Patterns framework. In the following sections we will discuss how a few of our partners have taken advantage of the Validated Patterns framework. + +== Intel +[#intel] + +=== AMX Accelerated Hardware + +[#intel-mcgo] +image::landscape-partner-intel-mcgo.png[Intel AMX Multi-Cloud GitOps] + +[#intel-md] +image::landscape-partner-intel-mcgo.png[Intel AMX Medical Diagnosis] + +== Portworx +[#pwx-mcgo] +image::landscape-partner-portworx.png[Portworx Multi-Cloud GitOps] + +== Cisco with Portworx +[#cisco-pwx-mcgo] +image::landscape-partner-cisco-portworx.png[Cisco with Portworx Multi-Cloud GitOps] + +== Veeam Kasten +[#veeam-mcgo] +image::landscape-partner-veeam.png[Veeam Multi-Cloud GitOps] diff --git a/content/workshop/patterns-operator.adoc b/content/workshop/patterns-operator.adoc new file mode 100644 index 000000000..5239d1d19 --- /dev/null +++ b/content/workshop/patterns-operator.adoc @@ -0,0 +1,30 @@ +--- +menu: + workshop: + parent: Validated Patterns +title: Patterns Operator +weight: 53 +--- + +:toc: +include::modules/comm-attributes.adoc[] +:imagesdir: /images/workshop + +[#features] +== Validated Operator Features + +* Available as a community operator in Operator Hub +* Interrogates the cluster and uses information to personalize the pattern with argo/helm values +* Allows you to pick a repository for the pattern and to select a branch version (e.g. `stable`) +* Deploys OpenShift GitOps with the correct `values-hub` +* Regardless of how you deploy the pattern, the operator is still in use + +[IMPORTANT] +.Loading Secrets +==== +`make load-secrets` is required when you create a Pattern CR directly in the OpenShift console _unless_ you use the Patterns Catalog UI. +==== + +image::vp-operator-community.png[Community Operator] + +image::olm-vp-operator.png[OLM] diff --git a/content/workshop/patterns-wrapper-script.adoc b/content/workshop/patterns-wrapper-script.adoc new file mode 100644 index 000000000..0add7e7ef --- /dev/null +++ b/content/workshop/patterns-wrapper-script.adoc @@ -0,0 +1,80 @@ +--- +menu: + workshop: + parent: Validated Patterns +title: Patterns Wrapper Script +weight: 54 +--- + +:toc: +include::modules/comm-attributes.adoc[] + +== Using the pattern.sh script + +[#about] + +[source,bash] +---- +$ ./pattern.sh + + 1 Welcome to the Validate Pattern utility container + 111 It contains all the needed components to install a pattern. + 1 1 + 111 111 Please run the following for more information: + 1 1 + 111 111 ./pattern.sh make help + 1 1 1 1 +111 111 111 111 + +---- + +* Used to deploy the pattern from the command-line +* Deploys the operator with a repository and branch from the local clone of the repo +* Provides a way to invoke the container which has all of the pre-requisites software installed (e.g. python, ansible, make) +* Improved User Experience when deploying Patterns from the command line +* When installing a pattern use: + +[source,bash] +---- +./pattern.sh make install +---- +* When loading secrets into your deployment +[source,bash] +---- +./pattern.sh make load-secrets +---- + +IMPORTANT: pattern.sh uses your origin remote and current branch. To change the branch after a deployment, run `make install` in the new branch + +[#help] +== Help Menu + +`pattern.sh` comes with a help menu, to use it: + +[source,bash] +---- +$ ./pattern.sh make help + +For a complete guide to these targets and the available overrides, please visit https://validatedpatterns.io/blog/2025-08-29-new-common-makefile-structure/ + +Usage: + make + help Print this help message + +Pattern Install Tasks + show Shows the template that would be applied by the `make install` target + operator-deploy operator-upgrade Installs/updates the pattern on a cluster (DOES NOT load secrets) + install Installs the pattern onto a cluster (Loads secrets as well if configured) + uninstall (EXPERIMENTAL) See https://validatedpatterns.io/blog/2026-02-16-pattern-uninstall/. + load-secrets Loads secrets onto the cluster (unless explicitly disabled in values-global.yaml) + +Validation Tasks + validate-prereq verify pre-requisites + validate-origin verify the git origin is available + validate-cluster Do some cluster validations before installing + validate-schema validates values files against schema in common/clustergroup + argo-healthcheck Checks if all argo applications are synced +---- + +The link:https://validatedpatterns.io/blog/2025-08-29-new-common-makefile-structure/#_using_makefile_common[link in the help text] describes the available arguments and how to use them for each of +the common Makefile targets. diff --git a/content/workshop/repo-fork.md b/content/workshop/repo-fork.md new file mode 100644 index 000000000..549c38f28 --- /dev/null +++ b/content/workshop/repo-fork.md @@ -0,0 +1,36 @@ +--- +menu: + workshop: + parent: Validated Patterns +title: To Fork or Not to Fork +weight: 55 +--- + +## Why fork? + +![fork](/images/workshop/fork.png) + +* You need to be able to make changes for your own organization's needs +* GitOps is all about git, merging changes safely is foundational +* Some changes make sense to be merged upstream +* You can make a pull request (PR) on the upstream pattern + +## Typical recommended fork structure + +```mermaid +graph LR + upstream["validatedpatterns/pattern-repo
(upstream)"] -->|fork| origin["your-org/pattern-repo
(origin)"] + origin -->|clone| local["Local Workstation"] + local -->|"git push"| origin + origin -.->|"pull request"| upstream + upstream -.->|"git fetch upstream"| local +``` + +**Git Workflow** + +* Find the pattern closest to the solution you require + * Often this is [multicloud-gitops](https://github.com/validatedpatterns/multicloud-gitops) +* Fork the repository into your organization or personal account +* Clone your fork to your local workstation +* Deploy / Change / Test +* Create pull requests to the repo and merge diff --git a/content/workshop/secrets-loading.adoc b/content/workshop/secrets-loading.adoc new file mode 100644 index 000000000..0e08a6ee4 --- /dev/null +++ b/content/workshop/secrets-loading.adoc @@ -0,0 +1,147 @@ +--- +menu: + workshop: + parent: Secrets Management +title: Secrets Loading +weight: 62 +--- + +:toc: +include::modules/comm-attributes.adoc[] + +[#secretLoading] +== Secrets Loading + +Loading secrets in the vault is usually done by running `./pattern.sh make load-secrets` from the Pattern's repository. This runs ansible code that parses local yaml files containing the secrets to be uploaded to the Vault running on the Hub + +The secrets needed for the pattern are in the `values-secret.yaml.template` file in the pattern's repository. + +Copy `values-secret.yaml.template` to `~/values-secret-multicloud-gitops.yaml` and edit it according to your needs + +TIP: You could also run `./pattern.sh make load-secrets` to run it through a container without installing too many dependencies + +Please encrypt the file with `ansible-vault encrypt ~/values-secret-multicloud-gitops.yaml` (i.e. no clear-text secrets at rest) + +NOTE: `make load-secrets` will prompt for the decryption key if it is encrypted + +It looks for files in the following order (and stops as soon as one is found): + +* ~/.config/validatedpatterns/values-secret-.yaml +* ~/values-secret-.yaml (e.g. ~/values-secret-multicloud-gitops.yaml) +* /values-secret.yaml.template + +[.TIP] +==== +An alternative yaml file path can be specified by setting the **VALUES_SECRET** environment variable: + +export VALUES_SECRET=~/foo/bar.yaml; ./pattern.sh make load-secrets +==== + +[#valuesecret] +== Secrets Template in the Patterns + +Two file format versions are understood by the loading mechanism: +"version: 1.0" (default) and "version: 2.0". **Please specify and use "version: 2.0" when possible** +Version 2.0 supports file-uploading, prompting for secrets, base64-encoding, reading secrets from INI-files (e.g. ~/.aws/credentials), uploading to multiple vault paths and generating secrets inside Vault directly using password policies +Version 1.0 is a lot more limited in terms of capabilities and deprecated +Both versions have corresponding JSON schemas: link:https://github.com/validatedpatterns/common/blob/main/ansible/roles/vault_utils/values-secrets.v1.schema.json[V1] and link:https://github.com/validatedpatterns/common/blob/main/ansible/roles/vault_utils/values-secrets.v2.schema.json[V2] +Complete examples can be found link:https://github.com/validatedpatterns/common/blob/main/ansible/roles/vault_utils/README.md[here] + +[#policy] + +[source,yaml] +---- +version: "2.0" + +secrets: + - name: config-demo + vaultPrefixes: + - global + fields: + - name: secret + onMissingValue: generate + vaultPolicy: validatedPatternDefaultPolicy +---- + +[.IMPORTANT] +==== + +* The validatedPatternDefaultPolicy is a default password policy that is guaranteed to exist +* It is defined link:https://github.com/validatedpatterns/common/blob/main/ansible/plugins/module_utils/load_secrets_v2.py#L28[here] +* The secret is 20 character longs (min 1 lowercase, min 1 uppercase, min 1 number, min 1 special char) +* It is possible to create a custom policy and use that one to generate secrets randomly + +==== + +[#secretexamples] +=== Secret Examples + +[#certificate] +[source,yaml] +---- +version: "2.0" +secrets: + - name: config-demo + vaultMount: secret + vaultPrefixes: + - region-one + - snowflake + fields: + - name: secretfile + path: /tmp/ca.crt + onMissingValue: prompt + base64: true + prompt: "Insert path to Certificate Authority" +---- + +This Will prompt the user with: + +* Insert path to Certificate Authority [/tmp/ca.crt]: +* Pressing only enter will accept the default in [] (no echo on input!!) +"/tmp/ca.crt" will be read, base64 encoded and uploaded +* It will be uploaded as an attribute called "secretfile" to two different vault keys: +** secret/region-one/config-demo +** secret/snowflake/config-demo + +[#awscreds] +[source,yaml] +---- +version: "2.0" + +secrets: + - name: aws + fields: + - name: aws_access_key_id + ini_file: ~/.aws/credentials + ini_section: default + ini_key: aws_access_key_id + - name: aws_secret_access_key + ini_file: ~/.aws/credentials + ini_section: default + ini_key: aws_secret_access_key +---- + +This reads `~/.aws/credentials` and uses the `[default]` section to look for the `aws_access_key_id` and `aws_secret_access_key` in the file + +[#custompolicy] +[source,yaml] +---- +version: "2.0" +vaultPolicies: + basicPolicy: | + length=10 + rule "charset" { charset = "abcdefghijklmnopqrstuvwxyz" min-chars = 1 } + rule "charset" { charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" min-chars = 1 } + rule "charset" { charset = "0123456789" min-chars = 1 } +secrets: + - name: config-demo + vaultPrefixes: + - region-one + fields: + - name: secret + onMissingValue: generate + override: true + vaultPolicy: basicPolicy +---- + +This will use the `basicPolicy` to generate a random value as an attribute called `secret` in the `secret/region-one/config-demo` vault path diff --git a/content/workshop/secrets.adoc b/content/workshop/secrets.adoc new file mode 100644 index 000000000..961b44999 --- /dev/null +++ b/content/workshop/secrets.adoc @@ -0,0 +1,49 @@ +--- +menu: workshop +title: Secrets Management +weight: 60 +--- + +:toc: +include::modules/comm-attributes.adoc[] + +[#secretsGitops] +== Secrets Management vs. GitOps + +* GitOps' entire premise is to use Git as the source of truth for infrastructure and application configuration +* Automation is in place (ArgoCD) to translate Git's source of truth into infrastructure applications inside OpenShift +* Storing confidential data (passwords, private keys, secrets, authentication tokens, ...) in Git is fundamentally a security vulnerability and should be avoided (even if the Git repository is private and access control is in place) +* Once confidential data is out in clear-text (or in some reversible form), it has to be considered as compromised + +[#approaches] +== Approaches + +* There are two fundamental approaches to manage secret material within a GitOps context +* **Encrypted Secrets** stored inside Git repositories +* **References to Secrets** stored inside Git repositories and the actual secret is stored somewhere else + +[#encryptedSecrets] +=== Encrypted Secrets + +* Secrets are stored in an encrypted form inside the Git repository +* Automation and helper tools do the decryption to create Kubernetes secrets from them +* A number of projects exist that implement this approach: +** link:https://github.com/bitnami-labs/sealed-secrets[Sealed Secrets] +** link:https://github.com/mozilla/sops[Mozilla's Secret OPerationS (SOPS)] +** Other smaller ones (link:https://github.com/kapicorp/tesoro[Tesoro], link:https://github.com/Soluto/kamus[Kamus]) + +These projects provide an easy way to encrypt and decrypt the secrets, and make sure that only authorized users can access them. +The encryption method and key management should be considered carefully as they are important to ensure the security of the secrets. + +IMPORTANT: You should evaluate and choose the right project that fits your specific use case and requirements. + +[#secretReferences] +=== References to Secrets + +This approach requires two main parts: + +* A Secret Management System (link:https://www.vaultproject.io/[HashiCorp Vault], link:https://www.conjur.org/[Conjur], link:https://lyft.github.io/confidant/[Confidant]) +* A controller that retrieves the secret from the Secret Management System and translates it to a Kubernetes Secret or injects it into the Pod directly (link:https://external-secrets.io/v0.7.0/[External Secrets Operators], link:https://github.com/kubernetes-sigs/secrets-store-csi-driver[Kubernetes Secrets Store CSI Driver], link:https://developer.hashicorp.com/vault/docs/platform/k8s/injector[Vault Agent Injector]) +* Secrets are uploaded/created directly into the Secret Management System +* References to secrets are stored in Git +* A controller reads the references to secrets and translates them into Kubernetes secrets diff --git a/content/workshop/self-contained.adoc b/content/workshop/self-contained.adoc new file mode 100644 index 000000000..c03db7709 --- /dev/null +++ b/content/workshop/self-contained.adoc @@ -0,0 +1,50 @@ +--- +menu: workshop +title: Self-Contained Patterns +weight: 70 +--- + +:toc: +include::modules/comm-attributes.adoc[] +:imagesdir: /images/workshop + +[#contained] + +image::complete-pattern.png[] + +Every Pattern has resources that make it unique: + +* Container Images +* Containerfiles (aka: Dockerfiles) +* Helm Charts +* Kustomize Manifests +* Kubernetes Manifests +* Ansible Codes +* Shell/Other programming language scripts + +Where or would I add them to the pattern? + +|=== +|**Resource Type**|**Placement** +| `kubernetes manifests (.yaml)` +| They can either be converted to a Helm chart, or the raw manifests can be placed in a directory under `charts/` +|`kustomize manifests` +| Kustomize manifests can be placed in a directory under `charts/` or another path (e.g. `kustomize`) could be used +|`Ansible` +| Ansible roles and playbooks can be deployed through the imperative framework, or via kubernetes job resources in a Helm chart +|`Scripts` +| Shell scripts and other programs that are required for applications can be converted to a configMap and mounted as a volume in a pod (job) + +|=== + + +IMPORTANT: The pattern should be able to deploy all the resources needed to run your applications. + +[#thoughts] +=== Thoughts on Self-Contained Patterns + +* Reusability / Repurposing becomes a serious problem when these resources are behind paywalls, private github repositories and registries. + +* Patterns designed and delivered for public consumption should have all of its resources available in a public repository and container registry + +* Self-contained patterns is letting the cluster build and deploy the container image used by applications (where it makes sense). diff --git a/content/workshop/syncwave-hooks.adoc b/content/workshop/syncwave-hooks.adoc new file mode 100644 index 000000000..73c0ed363 --- /dev/null +++ b/content/workshop/syncwave-hooks.adoc @@ -0,0 +1,242 @@ +--- +menu: + workshop: + parent: GitOps Rough Edges +title: Sync Waves and Hooks +weight: 81 +--- + +:toc: +include::modules/comm-attributes.adoc[] +:imagesdir: /images/workshop + +https://argoproj.github.io/argo-cd/user-guide/sync-waves/[Sync waves,window='_blank'] +are used in Argo CD to order how manifests are applied to the cluster. + +https://argoproj.github.io/argo-cd/user-guide/resource_hooks/[Resource +hooks,window='_blank'] break up the delivery of these manifests into different +phases. + +Using a combination of sync waves and resource hooks, you can control how your +application rolls out. + +This example will take you through the following steps: + +* Using sync waves to order deployment +* Exploring resource hooks +* Using sync waves and hooks together + +The sample application that we will deploy is a TODO application with a database +and, apart from deployment files, sync waves and resource hooks are used: + +image::todo-app.png[] + +[#using_syncwaves] +== Using Sync Waves + +A sync wave is a way to order how Argo CD applies the manifests that are stored +in git. All manifests have a wave of zero by default, but you can set these by +using the `argocd.argoproj.io/sync-wave` annotation. + +Example: + +[source,yaml] +---- +metadata: + annotations: + argocd.argoproj.io/sync-wave: "2" +---- + +The wave can also be negative as well. + +[source,yaml] +---- +metadata: + annotations: + argocd.argoproj.io/sync-wave: "-5" +---- + +When Argo CD starts a sync action, the manifests get placed in the following +order: + +* The Phase that they're in (we'll cover phases in the next section) +* The wave the resource is annotated in (starting from the lowest value to the + highest) +* By kind (Namespaces first, then services, then deployments, etc ...) +* By name (ascending order) + +Read more about sync waves on the +https://argoproj.github.io/argo-cd/user-guide/sync-waves/#how-do-i-configure-waves[official +documentation site,window='_blank']. + +[#exploring_the_manifests_waves] +=== Exploring Sync Wave Manifests + +The sample application that you will deploy has several waves. + +First, *PostgreSQL* with sync wave *0*. It has a Deployment: + +[source,yaml] +---- +include::modules/workshop-examples/todo/postgres-deployment.yaml[] +---- + +The *PostgreSQL Service* with sync wave *0*: + +[source,yaml] +---- +include::modules/workshop-examples/todo/postgres-service.yaml[] +---- + +Second, *Database table creation* with sync wave *1*: + +[source,yaml] +---- +include::modules/workshop-examples/todo/postgres-create-table.yaml[] +---- + +The *TODO application deployment* with sync wave *2*: + +[source,yaml] +---- +include::modules/workshop-examples/todo/todo-deployment.yaml[] +---- + +The *TODO Service* with sync wave *2*: + +[source,yaml] +---- +include::modules/workshop-examples/todo/todo-service.yaml[] +---- + +The *TODO Route* with sync wave *3*: + +[source,yaml] +---- +include::modules/workshop-examples/todo/todo-route.yaml[] +---- + +First, the PostgreSQL Deployment will be applied. After that reports healthy, +Argo CD will continue with the rest of resources. + +NOTE: Argo CD won't apply the next manifest in a wave until the previous reports "healthy". + +[#using_resource_hooks] +== Using Resource Hooks + +Now that you're familiar with sync waves, we can begin exploring applying +manifests in phases using `resource hooks`. + +Controlling your sync operation can be further refined by using +hooks. These hooks can run before, during, and after a sync +operation. These hooks are: + +* **PreSync** - Runs before the sync operation. This can be something like a + database backup before a schema change +* **Sync** - Runs after `PreSync` has successfully ran. This will run alongside + your normal manifests. +* **PostSync** - Runs after `Sync` has ran successfully. This can be something + like a Slack message or an email notification. +* **SyncFail** - Runs if the `Sync` operation has failed. This is also used to + send notifications or do other evasive actions. + +To enable a sync, annotate the specific object manifest with +`argocd.argoproj.io/hook` with the type of sync you want to use for that +resource. For example, if you wanted to use the `PreSync` hook: + +[source,yaml] +---- +metadata: + annotations: + argocd.argoproj.io/hook: PreSync +---- + +You can also have the hooks be deleted after a successful/unsuccessful run. + +* **HookSucceeded** - The resource will be deleted after it has succeeded. +* **HookFailed** - The resource will be deleted if it has failed. +* **BeforeHookCreation** - The resource will be deleted before a new one is + created (when a new sync is triggered). + +You can apply these with the `argocd.argoproj.io/hook-delete-policy` +annotation. For example: + +[source,yaml] +---- +metadata: + annotations: + argocd.argoproj.io/hook: PostSync + argocd.argoproj.io/hook-delete-policy: HookSucceeded +---- + +IMPORTANT: Since a sync can fail in any phase, you can come to a situation where the application never reports healthy! + +Although hooks can be any resource, they are usually Pods and/or Jobs. + +To read more about resource hooks, consult the +https://argoproj.github.io/argo-cd/user-guide/resource_hooks[official +documentation] + +[#exploring_the_manifests_hooks] +=== Exploring Resource Hook Manifests + +Take a look at this `PostSync` manifest which sends an HTTP request to insert a +new TODO item: + +[source,yaml] +---- +include::modules/workshop-examples/todo/todo-insert-data.yaml[] +---- + +The execution order can be seen in the following diagram: + +image::presyncpost.png[] + +[#deploying_the_application] +== Deploying The Application + +Taking a look at this manifest file: `todo-application.yaml`: + +[source,yaml] +---- +include::modules/workshop-examples/todo-application.yaml[] +---- + +On the Argo CD WebUI, you should see another application appear. + +image::todo-card.png[TODO Card] + +Clicking on this "card" should take you over to the tree view. + +image::todo-argocd.png[TODO Tree] + +Observe the sync process. You will see the order that the resource has been applied, first the namespace creation and last the creation of Route to access the application. + +Once the application is fully synced. Take a look at the pods and jobs in +the namespace: + +[source,bash] +---- +oc get pods -n todo +---- + +You should see that the Job is finished, but still there. + +[source,bash] +---- +NAME READY STATUS RESTARTS AGE +postgresql-599467fd86-cgj9v 1/1 Running 0 32s +todo-gitops-679d88f6f4-v4djp 1/1 Running 0 19s +todo-table-xhddk 0/1 Completed 0 27s +---- + +Your application should look like this: + +image::todo-app-screenshot.png[TODO] + +The `todo-insert` Job is not shown as it was configured to be deleted if succeeded: + +[source, yaml] +---- +argocd.argoproj.io/hook-delete-policy: HookSucceeded +---- diff --git a/content/workshop/tiers.adoc b/content/workshop/tiers.adoc new file mode 100644 index 000000000..9b80ca859 --- /dev/null +++ b/content/workshop/tiers.adoc @@ -0,0 +1,40 @@ +--- +menu: workshop +title: Pattern Tiers +weight: 40 +--- + +:toc: +include::modules/comm-attributes.adoc[] + +== Tiers +[#about] +The different tiers of Validated Patterns are designed to facilitate ongoing maintenance, support, and testing effort for a pattern. To contribute to a pattern that suits your solution or to learn about onboarding your own pattern, understand the following pattern tiers. + +|=== +|**Tier**|**Description**|**More Information** + +|**Sandbox** +a| * entrypoint for onboarding your Pattern + * able to deploy onto a freshly install OpenShift cluster without modifications or tuning + * must include a top-level README +| link:/contribute/sandbox/[Details] + +|**Tested** +a| * must include a standardized architecture diagram using (or conforming to) the standard Validated Patterns tooling + * must ensure the test plan passes at least once per quarter + * must include a written guide for others to follow when demonstrating the platform +| link:/contribute/tested/[Details] + +|**Maintained** +a| * functional on all currently supported/extended update support (EUS) versions of OpenShift + * must fix breakage in a timely manner + * must document their support policy +| link:/contribute/maintained/[Details] + +|=== + +== Additional Resources +[#resources] + +For more details, see the link:/learn/about-pattern-tiers-types/[Pattern Tier Types] page and the link:/learn/implementation[Implementation Requirements] guide. diff --git a/content/workshop/values-files.adoc b/content/workshop/values-files.adoc new file mode 100644 index 000000000..fb154cf5f --- /dev/null +++ b/content/workshop/values-files.adoc @@ -0,0 +1,69 @@ +--- +menu: + workshop: + parent: Validated Patterns +title: Values Files +weight: 51 +--- + +:toc: +include::modules/comm-attributes.adoc[] +:imagesdir: /images/workshop + +== Values Files +[#values] + +A variety of values files are used in the Patterns framework - but the three most common you will come across will be: + +|=== +|**Values File Name**|**Purpose** +| `values-` +| This is where the bill of materials for the pattern resides. Applications and Products deployed by the pattern are driven from this values file. +|`values-global` +| Used for setting values across all clusters and resources in the pattern. The values are used by Helm charts. +|`values-secret` +| Stored locally and `NOT` in git, this template allows you declare secrets to be distributed to the cluster +|=== + +In addition to the most common values files, we have also implemented the ability to use specialized values files so the same charts can be used to support multiple cloud providers, OpenShift versions and different environments. + +image::consuming-valuesSpecializedTypes.png[] + +When it comes to values and especially values in Helm - order matters. We have to take care when defining values that we don't accidentally overwrite or remove a value that we need. + +image::consuming-patternValuesFiles.png[] + +If we review the `values-hub.yaml` we can see where and how resources are declared: + +[source,yaml,subs="+macros,attributes+"] +---- +clusterGroup: + name: hub + + namespaces: + - open-cluster-management + - vault + - golang-external-secrets + - quay-enterprise + + subscriptions: + - name: advanced-cluster-management + namespace: open-cluster-management + + applications: + - name: quay-registry + namespace: quay-enterprise + path: charts/hub/quay +---- + +|=== +|**Key**|**Purpose** +| `namespaces` +| List (or map) of namespaces expected to be created +|`subscriptions` +| List of operators to be deployed on the cluster. If necessary, we can override default values for namespace, source, channel..etc +|`applications` +| Resource consumed by ArgoCD to define an application to be deployed +|=== + +NOTE: Sometimes we may need to override a default - an example of this is needing to create an OperatorGroup that doesn't monitor namespaces - check out link:https://validatedpatterns.io/blog/2023-12-15-understanding-namespaces/[this blog] to learn how! diff --git a/layouts/partials/menu-workshop.html b/layouts/partials/menu-workshop.html new file mode 100644 index 000000000..9bcd42e3f --- /dev/null +++ b/layouts/partials/menu-workshop.html @@ -0,0 +1,50 @@ +{{ $currentPage := . }} + +
+ +
diff --git a/layouts/partials/page-navigation.html b/layouts/partials/page-navigation.html index 6e17d50da..ce0257f7a 100644 --- a/layouts/partials/page-navigation.html +++ b/layouts/partials/page-navigation.html @@ -3,10 +3,11 @@ {{ $nextPage := false }} {{ $prevPage := false }} -{{ if eq .Section "learn" }} - {{/* For learn section, use the menu system to get the correct order */}} +{{ if or (eq .Section "learn") (eq .Section "workshop") }} + {{/* For learn/workshop sections, use the menu system to get the correct order */}} + {{ $menuName := .Section }} {{ $allMenuItems := slice }} - {{ range .Site.Menus.learn }} + {{ range index .Site.Menus $menuName }} {{ if .HasChildren }} {{/* Add parent */}} {{ $allMenuItems = $allMenuItems | append . }} @@ -21,21 +22,29 @@ {{ end }} {{/* Find current page index in menu items */}} - {{ $currentIndex := 0 }} + {{ $currentIndex := -1 }} {{ range $index, $item := $allMenuItems }} {{ if eq $item.URL $currentPage.RelPermalink }} {{ $currentIndex = $index }} {{ end }} {{ end }} - {{/* Get previous and next pages */}} - {{ if gt $currentIndex 0 }} - {{ $prevItem := index $allMenuItems (sub $currentIndex 1) }} - {{ $prevPage = .Site.GetPage $prevItem.URL }} - {{ end }} - {{ if lt $currentIndex (sub (len $allMenuItems) 1) }} - {{ $nextItem := index $allMenuItems (add $currentIndex 1) }} - {{ $nextPage = .Site.GetPage $nextItem.URL }} + {{ if eq $currentIndex -1 }} + {{/* Section landing page (not in menu) — next is the first menu item */}} + {{ if gt (len $allMenuItems) 0 }} + {{ $nextItem := index $allMenuItems 0 }} + {{ $nextPage = .Site.GetPage $nextItem.URL }} + {{ end }} + {{ else }} + {{/* Get previous and next pages */}} + {{ if gt $currentIndex 0 }} + {{ $prevItem := index $allMenuItems (sub $currentIndex 1) }} + {{ $prevPage = .Site.GetPage $prevItem.URL }} + {{ end }} + {{ if lt $currentIndex (sub (len $allMenuItems) 1) }} + {{ $nextItem := index $allMenuItems (add $currentIndex 1) }} + {{ $nextPage = .Site.GetPage $nextItem.URL }} + {{ end }} {{ end }} {{ else }} diff --git a/layouts/workshop/list.html b/layouts/workshop/list.html new file mode 100644 index 000000000..3b2988ebc --- /dev/null +++ b/layouts/workshop/list.html @@ -0,0 +1,48 @@ +{{ define "main" }} + {{ $section := .Site.GetPage "section" .Section }} +
+
+ {{ partial "menu-workshop.html" . }} +
+
+
+
+ {{ partial "toc-mobile.html" . }} + {{ partial "page-toolbar.html" . }} +
+ {{ partial "toc.html" . }} +
+ {{ .Content }} + + + {{ partial "page-navigation.html" . }} + + +
+
+ + {{ if .File }} + + + Edit this page + + + + + Open a documentation issue + + {{ end }} +
+
+
+
+
+ {{ partialCached "footer.html" . }} +
+{{ end }} diff --git a/layouts/workshop/single.html b/layouts/workshop/single.html new file mode 100644 index 000000000..3b2988ebc --- /dev/null +++ b/layouts/workshop/single.html @@ -0,0 +1,48 @@ +{{ define "main" }} + {{ $section := .Site.GetPage "section" .Section }} +
+
+ {{ partial "menu-workshop.html" . }} +
+
+
+
+ {{ partial "toc-mobile.html" . }} + {{ partial "page-toolbar.html" . }} +
+ {{ partial "toc.html" . }} +
+ {{ .Content }} + + + {{ partial "page-navigation.html" . }} + + +
+
+ + {{ if .File }} + + + Edit this page + + + + + Open a documentation issue + + {{ end }} +
+
+
+
+
+ {{ partialCached "footer.html" . }} +
+{{ end }} diff --git a/modules/comm-attributes.adoc b/modules/comm-attributes.adoc index 7ed8d6c0d..ecf370945 100644 --- a/modules/comm-attributes.adoc +++ b/modules/comm-attributes.adoc @@ -193,3 +193,13 @@ :cgu-operator-first: Topology Aware Lifecycle Manager (TALM) :cgu-operator-full: Topology Aware Lifecycle Manager :cgu-operator: TALM +//Workshop +:cli: oc +:branch: main +:vp-gh-repo: https://github.com/validatedpatterns +:rhacm: Red{nbsp}Hat Advanced Cluster Management for Kubernetes +:rhacs: Red{nbsp}Hat Advanced Cluster Security for Kubernetes +:rhocp: Red{nbsp}Hat OpenShift Container Platform +:rhodf: Red{nbsp}Hat OpenShift Data Foundations +:eso: External Secrets Operator +:vault: HashiCorp Vault diff --git a/modules/workshop-examples/run.sh b/modules/workshop-examples/run.sh new file mode 100644 index 000000000..94e0f20e6 --- /dev/null +++ b/modules/workshop-examples/run.sh @@ -0,0 +1,2 @@ +#!/bin/sh +echo "Hello World" diff --git a/modules/workshop-examples/todo-application.yaml b/modules/workshop-examples/todo-application.yaml new file mode 100644 index 000000000..c19ff0251 --- /dev/null +++ b/modules/workshop-examples/todo-application.yaml @@ -0,0 +1,17 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: todo-app +spec: + destination: + namespace: user$USERNUM-todo + server: https://kubernetes.default.svc + project: default + source: + path: documentation/modules/ROOT/examples/todo + repoURL: https://github.com/openshiftdemos/openshift-gitops-workshop + targetRevision: master + syncPolicy: + automated: + prune: true + selfHeal: false diff --git a/modules/workshop-examples/todo/postgres-create-table.yaml b/modules/workshop-examples/todo/postgres-create-table.yaml new file mode 100644 index 000000000..18addc53d --- /dev/null +++ b/modules/workshop-examples/todo/postgres-create-table.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: todo-table + annotations: + argocd.argoproj.io/sync-wave: "1" +spec: + template: + spec: + containers: + - name: postgresql-client + image: postgres:12 + imagePullPolicy: Always + env: + - name: PGPASSWORD + value: admin + command: ["psql"] + args: + [ + "--host=postgresql", + "--username=admin", + "--no-password", + "--dbname=todo", + "--command=create table Todo (id bigint not null,completed boolean not null,ordering integer,title varchar(255),url varchar(255),primary key (id));create sequence hibernate_sequence start with 1 increment by 1;", + ] + restartPolicy: Never + backoffLimit: 1 diff --git a/modules/workshop-examples/todo/postgres-deployment.yaml b/modules/workshop-examples/todo/postgres-deployment.yaml new file mode 100644 index 000000000..ee42622f4 --- /dev/null +++ b/modules/workshop-examples/todo/postgres-deployment.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgresql + annotations: + argocd.argoproj.io/sync-wave: "0" +spec: + selector: + matchLabels: + app: postgresql + template: + metadata: + labels: + app: postgresql + spec: + containers: + - name: postgresql + image: quay.io/redhatdemo/openshift-pgsql12-primary:centos7 + imagePullPolicy: Always + ports: + - name: tcp + containerPort: 5432 + env: + - name: PG_USER_PASSWORD + value: admin + - name: PG_USER_NAME + value: admin + - name: PG_DATABASE + value: todo + - name: PG_NETWORK_MASK + value: all diff --git a/modules/workshop-examples/todo/postgres-service.yaml b/modules/workshop-examples/todo/postgres-service.yaml new file mode 100644 index 000000000..e95d2e753 --- /dev/null +++ b/modules/workshop-examples/todo/postgres-service.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: postgresql + annotations: + argocd.argoproj.io/sync-wave: "0" +spec: + selector: + app: postgresql + ports: + - name: pgsql + port: 5432 + targetPort: 5432 diff --git a/modules/workshop-examples/todo/todo-deployment.yaml b/modules/workshop-examples/todo/todo-deployment.yaml new file mode 100644 index 000000000..a35d8fa8c --- /dev/null +++ b/modules/workshop-examples/todo/todo-deployment.yaml @@ -0,0 +1,46 @@ +--- +apiVersion: "v1" +kind: "ServiceAccount" +metadata: + labels: + app.kubernetes.io/name: "todo-gitops" + app.kubernetes.io/version: "1.0.0" + name: "todo-gitops" + annotations: + argocd.argoproj.io/sync-wave: "2" +--- +apiVersion: "apps/v1" +kind: "Deployment" +metadata: + labels: + app.kubernetes.io/name: "todo-gitops" + app.kubernetes.io/version: "1.0.0" + name: "todo-gitops" + annotations: + argocd.argoproj.io/sync-wave: "2" +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: "todo-gitops" + app.kubernetes.io/version: "1.0.0" + template: + metadata: + labels: + app.kubernetes.io/name: "todo-gitops" + app.kubernetes.io/version: "1.0.0" + spec: + containers: + - env: + - name: "KUBERNETES_NAMESPACE" + valueFrom: + fieldRef: + fieldPath: "metadata.namespace" + image: "quay.io/rhdevelopers/todo-gitops:1.0.0" + imagePullPolicy: "Always" + name: "todo-gitops" + ports: + - containerPort: 8080 + name: "http" + protocol: "TCP" + serviceAccount: "todo-gitops" diff --git a/modules/workshop-examples/todo/todo-insert-data.yaml b/modules/workshop-examples/todo/todo-insert-data.yaml new file mode 100644 index 000000000..9a34bcede --- /dev/null +++ b/modules/workshop-examples/todo/todo-insert-data.yaml @@ -0,0 +1,24 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: todo-insert + annotations: + argocd.argoproj.io/hook: PostSync # <1> + argocd.argoproj.io/hook-delete-policy: HookSucceeded +spec: + template: + spec: + containers: + - name: httpie + image: alpine/httpie:2.4.0 + imagePullPolicy: Always + command: ["http"] + args: + [ + "POST", + "todo-gitops:8080/api", + "title=Finish ArgoCD tutorial", + "--ignore-stdin" + ] + restartPolicy: Never + backoffLimit: 1 diff --git a/modules/workshop-examples/todo/todo-route.yaml b/modules/workshop-examples/todo/todo-route.yaml new file mode 100644 index 000000000..eac844b30 --- /dev/null +++ b/modules/workshop-examples/todo/todo-route.yaml @@ -0,0 +1,15 @@ +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + labels: + app: todo + name: todo + annotations: + argocd.argoproj.io/sync-wave: "3" +spec: + port: + targetPort: 8080 + to: + kind: Service + name: todo-gitops + weight: 100 diff --git a/modules/workshop-examples/todo/todo-service.yaml b/modules/workshop-examples/todo/todo-service.yaml new file mode 100644 index 000000000..04f1e7ee5 --- /dev/null +++ b/modules/workshop-examples/todo/todo-service.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: "v1" +kind: "Service" +metadata: + labels: + app.kubernetes.io/name: "todo-gitops" + app.kubernetes.io/version: "1.0.0" + name: "todo-gitops" + annotations: + argocd.argoproj.io/sync-wave: "2" +spec: + ports: + - name: "http" + port: 8080 + targetPort: 8080 + selector: + app.kubernetes.io/name: "todo-gitops" + app.kubernetes.io/version: "1.0.0" diff --git a/static/images/workshop/Linux-Penguin-Evolution-1.png b/static/images/workshop/Linux-Penguin-Evolution-1.png new file mode 100644 index 000000000..d9f22a9dd Binary files /dev/null and b/static/images/workshop/Linux-Penguin-Evolution-1.png differ diff --git a/static/images/workshop/complete-pattern.png b/static/images/workshop/complete-pattern.png new file mode 100644 index 000000000..cbff45943 Binary files /dev/null and b/static/images/workshop/complete-pattern.png differ diff --git a/static/images/workshop/consuming-common-solution-layering-modular.png b/static/images/workshop/consuming-common-solution-layering-modular.png new file mode 100644 index 000000000..6a2d598b8 Binary files /dev/null and b/static/images/workshop/consuming-common-solution-layering-modular.png differ diff --git a/static/images/workshop/consuming-common-solution-layering.png b/static/images/workshop/consuming-common-solution-layering.png new file mode 100644 index 000000000..882cd3eaf Binary files /dev/null and b/static/images/workshop/consuming-common-solution-layering.png differ diff --git a/static/images/workshop/consuming-common.png b/static/images/workshop/consuming-common.png new file mode 100644 index 000000000..bed665a49 Binary files /dev/null and b/static/images/workshop/consuming-common.png differ diff --git a/static/images/workshop/consuming-multipleArgos.png b/static/images/workshop/consuming-multipleArgos.png new file mode 100644 index 000000000..590e45a70 Binary files /dev/null and b/static/images/workshop/consuming-multipleArgos.png differ diff --git a/static/images/workshop/consuming-patternValuesFiles.png b/static/images/workshop/consuming-patternValuesFiles.png new file mode 100644 index 000000000..417eb67d2 Binary files /dev/null and b/static/images/workshop/consuming-patternValuesFiles.png differ diff --git a/static/images/workshop/consuming-valuesFileTypes.png b/static/images/workshop/consuming-valuesFileTypes.png new file mode 100644 index 000000000..fcb69d42b Binary files /dev/null and b/static/images/workshop/consuming-valuesFileTypes.png differ diff --git a/static/images/workshop/consuming-valuesSpecializedTypes.png b/static/images/workshop/consuming-valuesSpecializedTypes.png new file mode 100644 index 000000000..ce2029eba Binary files /dev/null and b/static/images/workshop/consuming-valuesSpecializedTypes.png differ diff --git a/static/images/workshop/consuming-vpWorkflow-hub.png b/static/images/workshop/consuming-vpWorkflow-hub.png new file mode 100644 index 000000000..5a341ce5b Binary files /dev/null and b/static/images/workshop/consuming-vpWorkflow-hub.png differ diff --git a/static/images/workshop/consuming-vpWorkflow-managedCluster.png b/static/images/workshop/consuming-vpWorkflow-managedCluster.png new file mode 100644 index 000000000..d7d94a78e Binary files /dev/null and b/static/images/workshop/consuming-vpWorkflow-managedCluster.png differ diff --git a/static/images/workshop/create-where2start.png b/static/images/workshop/create-where2start.png new file mode 100644 index 000000000..0a1db6727 Binary files /dev/null and b/static/images/workshop/create-where2start.png differ diff --git a/static/images/workshop/fork-structure.png b/static/images/workshop/fork-structure.png new file mode 100644 index 000000000..44cdf8776 Binary files /dev/null and b/static/images/workshop/fork-structure.png differ diff --git a/static/images/workshop/fork.png b/static/images/workshop/fork.png new file mode 100644 index 000000000..6ad2d0ce9 Binary files /dev/null and b/static/images/workshop/fork.png differ diff --git a/static/images/workshop/global-configdemo-vault.png b/static/images/workshop/global-configdemo-vault.png new file mode 100644 index 000000000..98aae0260 Binary files /dev/null and b/static/images/workshop/global-configdemo-vault.png differ diff --git a/static/images/workshop/introduction-batteriesIncluded.png b/static/images/workshop/introduction-batteriesIncluded.png new file mode 100644 index 000000000..bc8b9063d Binary files /dev/null and b/static/images/workshop/introduction-batteriesIncluded.png differ diff --git a/static/images/workshop/introduction-goals.png b/static/images/workshop/introduction-goals.png new file mode 100644 index 000000000..c9c5a8e49 Binary files /dev/null and b/static/images/workshop/introduction-goals.png differ diff --git a/static/images/workshop/introduction-pattern-benefits.png b/static/images/workshop/introduction-pattern-benefits.png new file mode 100644 index 000000000..f2e48d6ef Binary files /dev/null and b/static/images/workshop/introduction-pattern-benefits.png differ diff --git a/static/images/workshop/introduction-pattern-values.png b/static/images/workshop/introduction-pattern-values.png new file mode 100644 index 000000000..dd54fc2a4 Binary files /dev/null and b/static/images/workshop/introduction-pattern-values.png differ diff --git a/static/images/workshop/introduction-techSlide.png b/static/images/workshop/introduction-techSlide.png new file mode 100644 index 000000000..a59c10fa8 Binary files /dev/null and b/static/images/workshop/introduction-techSlide.png differ diff --git a/static/images/workshop/kubelogo.png b/static/images/workshop/kubelogo.png new file mode 100644 index 000000000..13cb03cbb Binary files /dev/null and b/static/images/workshop/kubelogo.png differ diff --git a/static/images/workshop/landscape-early24.png b/static/images/workshop/landscape-early24.png new file mode 100644 index 000000000..d6f3be717 Binary files /dev/null and b/static/images/workshop/landscape-early24.png differ diff --git a/static/images/workshop/landscape-july24.png b/static/images/workshop/landscape-july24.png new file mode 100644 index 000000000..d802fbbbf Binary files /dev/null and b/static/images/workshop/landscape-july24.png differ diff --git a/static/images/workshop/landscape-partner-cisco-portworx.png b/static/images/workshop/landscape-partner-cisco-portworx.png new file mode 100644 index 000000000..ec9142317 Binary files /dev/null and b/static/images/workshop/landscape-partner-cisco-portworx.png differ diff --git a/static/images/workshop/landscape-partner-intel-headerSlide.png b/static/images/workshop/landscape-partner-intel-headerSlide.png new file mode 100644 index 000000000..b1d88d7b9 Binary files /dev/null and b/static/images/workshop/landscape-partner-intel-headerSlide.png differ diff --git a/static/images/workshop/landscape-partner-intel-mcgo.png b/static/images/workshop/landscape-partner-intel-mcgo.png new file mode 100644 index 000000000..cd560c8a7 Binary files /dev/null and b/static/images/workshop/landscape-partner-intel-mcgo.png differ diff --git a/static/images/workshop/landscape-partner-intel-md.png b/static/images/workshop/landscape-partner-intel-md.png new file mode 100644 index 000000000..eaf4c7caf Binary files /dev/null and b/static/images/workshop/landscape-partner-intel-md.png differ diff --git a/static/images/workshop/landscape-partner-portworx.png b/static/images/workshop/landscape-partner-portworx.png new file mode 100644 index 000000000..051b55543 Binary files /dev/null and b/static/images/workshop/landscape-partner-portworx.png differ diff --git a/static/images/workshop/landscape-partner-titleSlide.png b/static/images/workshop/landscape-partner-titleSlide.png new file mode 100644 index 000000000..8a304f69d Binary files /dev/null and b/static/images/workshop/landscape-partner-titleSlide.png differ diff --git a/static/images/workshop/landscape-partner-veeam.png b/static/images/workshop/landscape-partner-veeam.png new file mode 100644 index 000000000..1e656dbbc Binary files /dev/null and b/static/images/workshop/landscape-partner-veeam.png differ diff --git a/static/images/workshop/landscape-q2cy26.png b/static/images/workshop/landscape-q2cy26.png new file mode 100644 index 000000000..6166e42e9 Binary files /dev/null and b/static/images/workshop/landscape-q2cy26.png differ diff --git a/static/images/workshop/multicluster-challenges.png b/static/images/workshop/multicluster-challenges.png new file mode 100644 index 000000000..8bcd428cb Binary files /dev/null and b/static/images/workshop/multicluster-challenges.png differ diff --git a/static/images/workshop/multicluster-rhacm-value.png b/static/images/workshop/multicluster-rhacm-value.png new file mode 100644 index 000000000..21cb94c85 Binary files /dev/null and b/static/images/workshop/multicluster-rhacm-value.png differ diff --git a/static/images/workshop/multisource-config.png b/static/images/workshop/multisource-config.png new file mode 100644 index 000000000..447a478e7 Binary files /dev/null and b/static/images/workshop/multisource-config.png differ diff --git a/static/images/workshop/olm-vp-operator.png b/static/images/workshop/olm-vp-operator.png new file mode 100644 index 000000000..bf9e3345f Binary files /dev/null and b/static/images/workshop/olm-vp-operator.png differ diff --git a/static/images/workshop/operator-install.png b/static/images/workshop/operator-install.png new file mode 100644 index 000000000..e5e14abc4 Binary files /dev/null and b/static/images/workshop/operator-install.png differ diff --git a/static/images/workshop/presyncpost.png b/static/images/workshop/presyncpost.png new file mode 100644 index 000000000..f041e3195 Binary files /dev/null and b/static/images/workshop/presyncpost.png differ diff --git a/static/images/workshop/todo-app-screenshot.png b/static/images/workshop/todo-app-screenshot.png new file mode 100644 index 000000000..6af04c5ff Binary files /dev/null and b/static/images/workshop/todo-app-screenshot.png differ diff --git a/static/images/workshop/todo-app.png b/static/images/workshop/todo-app.png new file mode 100644 index 000000000..19baaaeab Binary files /dev/null and b/static/images/workshop/todo-app.png differ diff --git a/static/images/workshop/todo-argocd.png b/static/images/workshop/todo-argocd.png new file mode 100644 index 000000000..e0f62de50 Binary files /dev/null and b/static/images/workshop/todo-argocd.png differ diff --git a/static/images/workshop/todo-card.png b/static/images/workshop/todo-card.png new file mode 100644 index 000000000..63be47aee Binary files /dev/null and b/static/images/workshop/todo-card.png differ diff --git a/static/images/workshop/unseal-vault-cronjob.png b/static/images/workshop/unseal-vault-cronjob.png new file mode 100644 index 000000000..529b77aca Binary files /dev/null and b/static/images/workshop/unseal-vault-cronjob.png differ diff --git a/static/images/workshop/vp-operator-community.png b/static/images/workshop/vp-operator-community.png new file mode 100644 index 000000000..f1a6f5655 Binary files /dev/null and b/static/images/workshop/vp-operator-community.png differ