diff --git a/pages/deployments/operator/architecture.md b/pages/deployments/operator/architecture.md index e1c99024..2d93a501 100644 --- a/pages/deployments/operator/architecture.md +++ b/pages/deployments/operator/architecture.md @@ -3,9 +3,9 @@ title: Plural Deployment Operator description: GitOps Management using the Plural Operator --- -The plural operator defines a set of CRDs that allow you to manage your deployments in a fully GitOps manner. The controller ultimately communicates with our core apis and acts effectively as a frontend to automate provisioning/deprovisioning the requisite resources. The CRD structures also imitate the patterns used by Flux and is interoperable with many Flux types (particularly those from its source controller), with modular distinct types for the various roles in deployments, e.g. git/helm repositories, clusters, and services. +The Plural operator defines a set of CRDs that allow you to manage your deployments in a fully GitOps manner. The controller ultimately communicates with our core apis and acts effectively as a frontend to automate provisioning/deprovisioning the requisite resources. The CRD structures also imitate the patterns used by Flux and is interoperable with many Flux types (particularly those from its source controller), with modular distinct types for the various roles in deployments, e.g. git/helm repositories, clusters, and services. -To illustrate the flexibility this model provides a very simple example to set up a helm multi-source deployment would look like this: +To illustrate the flexibility this model provides a very simple example to set up a helm multi-source deployment would look like this: ```yaml # helm repository to use for the service diff --git a/pages/deployments/pr-automation.md b/pages/deployments/pr-automation.md new file mode 100644 index 00000000..fbacf01d --- /dev/null +++ b/pages/deployments/pr-automation.md @@ -0,0 +1,10 @@ +--- +title: Pull Request Automation +description: Self-Service GitOps with PR Automation +--- + +GitOps and Infrastructure as Code workflows are extremely powerful, but they include a fair amount of manual care and feeding to operate longterm. Each reconfiguration requires a code change which usually involves manual human effort that is expensive and prone to error. Plural intends to solve that by providing a toolkit to automate on top of your IaC workflows using a number of key patterns: + +- custom Renovate build meant to provide turnkey setup for PR generation for dependency releases, like helm charts +- PR Automation Custom Resources to provide your own self-service workflows for either modifying existing resources in your codebase, or generating new services +- Pipelines driven by PR Automations, allowing your release process to be fully automated while still providing all the auditability benefits of GitOps diff --git a/pages/deployments/pr/crds.md b/pages/deployments/pr/crds.md new file mode 100644 index 00000000..02e08b0b --- /dev/null +++ b/pages/deployments/pr/crds.md @@ -0,0 +1,121 @@ +--- +title: PR Automation Custom Resources +description: Define your own Pull Request Automations with Kubernetes CRDs +--- + +You can use our CRD toolkit to generate new PR Automation flows easily. This can be as simple as generating pull requests for upgrading to a new kubernetes version in terraform, or it can involve more complex workflows. In this guide, we'll show how you can use it to provide self-service developer workspaces, a pretty common usecase for a lot of enterprises. + +## Defining Templates + +Most of the PR automation works off of Shopify's [Liquid](https://shopify.github.io/liquid/) templating engine. It's a well-documented, widely used templating library that frankly is a bit nicer than go's builtin text/template library. + +For this example there are a few templates that are worth mentioning: + +in `templates/service.yaml.liquid`: + +```yaml +apiVersion: deployments.plural.sh/v1alpha1 +kind: ServiceDeployment +metadata: + name: {{ context.name }} + namespace: infra +spec: + namespace: {{ context.name }} + git: + folder: workspaces + ref: main + repositoryRef: + kind: GitRepository + name: infra + namespace: infra + configurationRef: + name: mottmac-pull-creds + namespace: infra + helm: + version: "0.x.x" + chart: workloads + valuesFiles: + - {{ context.name }}.yaml + repository: + namespace: infra + name: workloads + clusterRef: + kind: Cluster + name: mgmt + namespace: infra +``` + +and `workspace.yaml.liquid`: + +```yaml +cluster: { { context.cluster } } +gitRepository: { { context.repo } } + +access: + write: + - groupName: { { context.group } } + +workloads: { { context.workloads } } +``` + +The first just generates a `ServiceDeployment` CRD (if you aren't familiar with this, feel free to look at the docs for the Plural operator) that will ultimately create the workspace service. The second is meant to define the helm values file for that service. The variables in `context` will be user specifed, and you can see it also takes a list of `workloads` that will be the individual components spawned by that service -- the workspace is in fact an app-of-apps. + +## PR Automation Spec + +Those templates are then used by a PR Automation resource like so: + +```yaml +apiVersion: deployments.plural.sh/v1alpha1 +kind: ScmConnection +metadata: + name: github +spec: + name: github + type: GITHUB +--- +apiVersion: deployments.plural.sh/v1alpha1 +kind: PrAutomation +metadata: + name: workspace-creator +spec: + name: workspace-creator + documentation: | + Sets up a PR to provision a new workspace for a team. This is fairly rudimentary at the moment for demonstration purposes + creates: + templates: + - source: templates/service.yaml.liquid # points to the template above + destination: 'apps/workspaces/{{ context.name }}.yaml' + external: false # tells us to source the template from within the repo + - source: templates/workspace.yaml.liquid # similar as above, pointing to above templates + destination: workspaces/{{ context.name }}.yaml + external: false + scmConnectionRef: + name: github + title: 'Adding workspace {{ context.name }} for {{ context.group }}' + message: "Adding workspace {{ context.name }} for {{ context.group }}\nWorkloads to be provisioned: [{{ context.workloads }}]" + identifier: pluralsh/pr-automation-demo # id slug for the repo this automation will be applied to + configuration: + - name: name + type: STRING + documentation: The name of this workspace + - name: cluster + type: STRING + documentation: The cluster this workspace deploys to + - name: repo + type: STRING + documentation: The repo it is sourced from + - name: group + type: STRING + documentation: A group w/ writer permissions for this workspace + - name: workloads + type: STRING + documentation: comma separated list of workloads +``` + +Tying this together, we've created a PR Automation that is referencing the above two yaml templates, and is configured to create new files for the results of applying each. When the automation spec is created, it will be viewable within your Plural console under the PR tab, and you'll be able to create the PR and fill out a form like: + +![](/assets/deployments/workspace-pr.png) + +When the automation is triggered, it will take the results of that form, pass them to the templates, print them to the configured files, and call the appropriate SCM api (Github's in this case) to generate the pull request. + +In this case, if the pull request is merged, there is already another service in place to sync the `apps/workspaces` folder, so it would deploy as the system polls in the new CRD in that folder and the new workspace would be provisioned. diff --git a/pages/deployments/pr/pipelines.md b/pages/deployments/pr/pipelines.md new file mode 100644 index 00000000..d458c131 --- /dev/null +++ b/pages/deployments/pr/pipelines.md @@ -0,0 +1,143 @@ +--- +title: PR Automation Pipelines +description: Use PR Automations to generate revisions throughout a Pipeline +--- + +Plural Pipelines do not ordinarily require human intervention to deploy the services within them, instead relying on common conventions like passing along git shas plus configured secrets to ferry along code changes. That said, there are still cases where you would like a PR to perform each update: + +- Robust GitOps flows where you need an auditable approval for each change +- Cases where other automations (eg GitOps app-of-apps or terraform) could interfere with the changes from a pipeline + +Plural PR Automation pipelines provide a simple but highly configurable means of providing extensible, auditable, yet automated workflows that can meet those sorts of constraints. + +## Setup + +Here's a quick and dirty dev to prod pipeline with pr automations to execute the changes in each stage: + +in `dev.yaml`: + +```yaml +apiVersion: deployments.plural.sh/v1alpha1 +kind: ServiceDeployment +metadata: + name: podinfo-dev +spec: + name: podinfo + namespace: podinfo + helm: + chart: podinfo + version: 6.5.3 # VERSION + repository: + name: podinfo + namespace: infra + clusterRef: + kind: Cluster + name: boot-staging + namespace: infra +``` + +in `prod.yaml`: + +```yaml +apiVersion: deployments.plural.sh/v1alpha1 +kind: ServiceDeployment +metadata: + name: podinfo-prod +spec: + name: podinfo + namespace: podinfo + helm: + chart: podinfo + version: 6.5.3 # VERSION + repository: + name: podinfo + namespace: infra + clusterRef: + kind: Cluster + name: boot-prod + namespace: infra +``` + +in `pipeline.yaml`: + +```yaml +apiVersion: deployments.plural.sh/v1alpha1 +kind: Pipeline +metadata: + name: pr-automation +spec: + stages: + - name: dev + services: + - serviceRef: + name: podinfo-dev + namespace: infra + criteria: + prAutomationRef: + name: podinfo-pipeline + - name: prod + services: + - serviceRef: + name: podinfo-prod + namespace: infra + criteria: + prAutomationRef: + name: podinfo-pipeline + edges: + - from: dev + to: prod + gates: + - name: approval-gate + type: APPROVAL +``` + +in `prautomation.yaml`: + +```yaml +apiVersion: deployments.plural.sh/v1alpha1 +kind: PrAutomation +metadata: + name: podinfo-pipeline +spec: + name: podinfo-pipeline + documentation: Updates the podinfo service to a specified helm version for any pipeline stage + updates: + regexReplacements: + - regex: 'version: (.*) # VERSION' + file: apps/pipeline/{{ context.pipeline.stage.name }}.yaml + replacement: 'version: {{ context.version }} # VERSION' + scmConnectionRef: + name: gh-test + title: 'Updating pod info prod helm version to {{ context.version }} (stage={{ context.pipeline.stage.name }})' + message: 'Updating pod info prod helm version to {{ context.version }} (stage={{ context.pipeline.stage.name }})' + identifier: pluralsh/plrl-boot-aws + configuration: + - name: version + type: STRING + documentation: The helm chart version to use +``` + +## How it works + +The `{dev,prod}.yaml` files configure the two podinfo services. Notice the `# VERSION` comment in each, that's simply designed to simplify the regex logic that's ultimately used by the pr automation in `prautomation.yaml` and is a generally useful trick in configuring these workflows. + +The PR Automation itself is templated, and utilizes the default `pipeline` object to infer the stage in which its applied to reduce the configuration burden for you. When triggered by the dev stage, it'll work off the `apps/pipeline/dev.yaml` file, and similarly the `apps/pipeline/prod.yaml` file for the prod stage. + +The Pipeline yaml file configures the pipeline end-to-end, and you can see each stage service has promotion criteria pointing to the configured pr automation. That tells the pipeline to trigger the pr creation process when that stage is active. To move from dev -> prod, a manual approval is required, as specified in the edge configuration. + +## Kicking a run off + +To initiate the pipeline, you need to create a pipeline context, which you can do easily via CRD, eg: + +```yaml +apiVersion: deployments.plural.sh/v1alpha1 +kind: PipelineContext +metadata: + name: podinfo-context +spec: + pipelineRef: + name: pr-automation + namespace: infra + context: + version: 6.5.4 +``` diff --git a/pages/deployments/terraform-interop.md b/pages/deployments/terraform-interop.md new file mode 100644 index 00000000..45c2e503 --- /dev/null +++ b/pages/deployments/terraform-interop.md @@ -0,0 +1,75 @@ +--- +title: Terraform Interop with Service Contexts +description: Communicate data between terraform and kubernetes using Service Contexts +--- + +A common and incredibly frustrating challenge with managing kubernetes, especially at scale, is sharing state between terraform and the common tools used to manage Kubernetes configuration like helm and kustomize. We've created a system called Service Contexts to facilitate this. At its core, it is simply named bundles of configuration that can be created via api (thus easily integrated with Terraform or Pulumi) and mounted to Plural services. Here's a simple example, involving setting up an IRSA role for external-dns: + +```tf +module "assumable_role_externaldns" { + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc" + version = "3.14.0" + create_role = true + role_name = "${var.cluster_name}-externaldns" + provider_url = replace(local.cluster_oidc_issuer_url, "https://", "") + role_policy_arns = [aws_iam_policy.externaldns.arn] # defined elsewhere + oidc_fully_qualified_subjects = ["system:serviceaccount:${var.namespace}:${var.externaldns_serviceaccount}"] +} + +resource "plural_service_context" "externaldns" { + name = "externaldns" + configuration = { + roleArn = module.assumeable_role_externaldns.this_iam_role_arn + } +} +``` + +You could then attach it to an externaldns service like so: + +```yaml +apiVersion: deployments.plural.sh/v1alpha1 +kind: ServiceDeployment +metadata: + name: external-dns + namespace: infra +spec: + namespace: external-dns + git: + folder: helm-values + ref: main + repositoryRef: + kind: GitRepository + name: infra + namespace: infra + contexts: + - externaldns # binds the externaldns context to this service + helm: + version: '6.14.1' + chart: external-dns + valuesFiles: + - external-dns.yaml.liquid # we're using a multi-source service sourcing this values file from `helm-values/external-dns.yaml.liquid` in the infra repo above + repository: + namespace: infra + name: external-dns + clusterRef: + kind: Cluster + name: target-cluster + namespace: infra +``` + +{% callout severity="info" %} +The `.liquid` extension on `external-dns.yaml.liquid` tells the deployment agent to attempt to template the values file, otherwise it will interpret it as plain yaml. +{% /callout %} + +Then in `helm-values/external-dns.yaml.liquid` you could easily template in the role arn like so: + +```yaml +serviceAccount: + create: true + annotations: + eks.amazonaws.com/role-arn: { { contexts.externaldns.roleArn } } +``` + +(You can of course layer on any additional externaldns configuration you'd like, we're only interested in the eks iam role attachment here) + +What this is doing is on each attempt to retemplate the service, we're pulling the current value of the context alongside the service and injecting it into the passed values file. This helps simplify the process of managing the disparate toolchains and their independent state systems and it also dramatically reduces the risk of drift throughout your infrastructure. diff --git a/public/assets/deployments/workspace-pr.png b/public/assets/deployments/workspace-pr.png new file mode 100644 index 00000000..72d4d037 Binary files /dev/null and b/public/assets/deployments/workspace-pr.png differ diff --git a/src/NavData.tsx b/src/NavData.tsx index c06ec7dd..90319829 100644 --- a/src/NavData.tsx +++ b/src/NavData.tsx @@ -174,6 +174,20 @@ const rootNavData: NavMenu = deepFreeze([ }, ], }, + { + href: '/deployments/pr-automation', + title: 'Pull Request Automation', + sections: [ + { + title: 'On Demand Pull Requests', + href: '/deployments/pr/crds', + }, + { + title: 'Pull Request Pipelines', + href: '/deployments/pr/pipelines', + }, + ], + }, { href: '/deployments/addons', title: 'Managed Kubernetes Add-Ons', @@ -206,6 +220,10 @@ const rootNavData: NavMenu = deepFreeze([ }, ], }, + { + href: '/deployments/terraform-interop', + title: 'Service Contexts and Terraform', + }, { href: '/deployments/pipelines', title: 'Pipelines', diff --git a/src/generated/pages.json b/src/generated/pages.json index 36a90752..16f72ab2 100644 --- a/src/generated/pages.json +++ b/src/generated/pages.json @@ -182,6 +182,15 @@ { "path": "/deployments/pipelines" }, + { + "path": "/deployments/pr/crds" + }, + { + "path": "/deployments/pr/pipelines" + }, + { + "path": "/deployments/pr-automation" + }, { "path": "/deployments/security-addons" }, @@ -191,6 +200,9 @@ { "path": "/deployments/services" }, + { + "path": "/deployments/terraform-interop" + }, { "path": "/deployments/upgrades" },