\ No newline at end of file
diff --git a/search/search_index.json b/search/search_index.json
index 22575a47..e73f27ca 100644
--- a/search/search_index.json
+++ b/search/search_index.json
@@ -1 +1 @@
-{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Overview","text":""},{"location":"#blueprints-for-crossplane-on-amazon-eks","title":"Blueprints for Crossplane on Amazon EKS","text":"
Note: AWS Blueprints for Crossplane on Amazon Elastic Kubernetes Service is under active development and should be considered a pre-production framework.
AWS Crossplane Blueprints is an open source repo to bootstrap Amazon Elastic Kubernetes Service Clusters. and provision AWS resources with a library of Crossplane Compositions (XRs) with Composite Resource Definitions (XRDs).
If you are new to Crossplane, it is highly recommended to get yourself familiarized with Crossplane concepts. The official documentation and this blog post are good starting points.
Compositions in this repository enable platform teams to define and offer bespoke AWS infrastructure APIs to the teams of application developers based on predefined Composite Resources (XRs), encompassing one or more of AWS Managed Resources (MRs)
This repo provides multiple options to bootstrap Amazon EKS Clusters with Crossplane and AWS Providers. Checkout the following README for full deployment configuration
Bootstrap EKS Cluster with eksctl
Bootstrap EKS Cluster with Terraform
\u2705 Configure the EKS cluster
Enable IRSA support for your EKS cluster for the necessary permissions to spin up other AWS services. Depending on the provider, refer to the bootstrap README for this configuration.
AWS Provider - Crossplane Compositions for AWS Services
Upbound AWS Provider - Upbound Crossplane Compositions for AWS Services
\u2705 Deploy the Examples
With the setup complete, you can then follow instructions on deploying crossplane compositions or managed resources you want to experiment with. Keep in mind that the list of compositions and managed resources in this repository are evolving.
Deploy the Examples by following this README
\u2705 Work with nested compositions.
Compositions can be nested to further define and abstract application specific needs.
Take a quick tour of a nested composition example
\u2705 Work with external secrets.
Crossplane can be configured to publish secrets external to the cluster in which it runs.
Try it out with this guide
\u2705 Check out the RDS day 2 operation doc
\u2705 Checkout example Gatekeeper configurations.
This library is licensed under the Apache 2.0 License.
"},{"location":"faq/","title":"Frequently Asked Questions","text":""},{"location":"faq/#timeouts-on-destroy","title":"Timeouts on destroy","text":"
Customers who are deleting their environments using terraform destroy may see timeout errors when VPCs are being deleted. This is due to a known issue in the vpc-cni
Customers may face a situation where ENIs that were attached to EKS managed nodes (same may apply to self-managed nodes) are not being deleted by the VPC CNI as expected which leads to IaC tool failures, such as:
ENIs are left on subnets
EKS managed security group which is attached to the ENI can\u2019t be deleted by EKS
The current recommendation is to execute cleanup in the following order:
delete all pods that have been created in the cluster.
For consuming Crossplane Blueprints, please see the Getting Started section. For exploring and trying out the patterns provided, please clone the project locally to quickly get up and running with a pattern. After cloning the project locally, cd into the pattern directory of your choice.
To provision the pattern, the typical steps of execution are as follows:
Once all of the resources have successfully been provisioned, the following command can be used to update the kubeconfig on your local machine and allow you to interact with your EKS Cluster using kubectl.
The examples will output the aws eks update-kubeconfig ... command as part of the Terraform apply output to simplify this process for users
Once you have updated your kubeconfig, you can verify that you are able to interact with your cluster by running the following command:
kubectl get nodes\n
This should return a list of the node(s) running in the cluster created. If any errors are encountered, please re-trace the steps above and consult the pattern's README.md for more details on any additional/specific steps that may be required.
Some resources may have been created that Terraform is not aware of that will cause issues when attempting to clean up the pattern. Please see the destroy.md for more details.
"},{"location":"patterns/debugging/","title":"Debugging CompositeResourceDefinitions (XRD) and Compositions","text":""},{"location":"patterns/debugging/#composite-resources-and-claim-overview","title":"Composite resources and claim overview","text":"
Most error messages are logged to resources' event field. Whenever your Composite Resources are not getting provisioned, follow the following: 1. Get the events for the root resource using kubectl describe or kubectl get event 2. If there are errors in the events, address them. 3. If no errors, follow its sub-resources. kubectl get <KIND> <NAME> -o=jsonpath='{.spec.resourceRef}{\" \"}{.spec.resourceRefs}' | jq 4. Go back to step 1 using one of resources returned by step 3.
Note: Debugging is also enabled for the AWS provider pods. You may find it useful to check the logs for the provider pods for extra information on failures. You can also disable logging here.
# kubectl get pods -n crossplane-system\nNAME READY STATUS RESTARTS AGE\ncrossplane-5b6896bb4c-mjr8x 1/1 Running 0 12d\ncrossplane-rbac-manager-7874897d59-fc9wf 1/1 Running 0 12d\nprovider-aws-f6a4a9bdba04-84ddf67474-z78nl 1/1 Running 0 12d\nprovider-kubernetes-cfae2275d58e-6b7bcf5bb5-2rjk2 1/1 Running 0 8d\n\n# For the AWS provider logs\n# kubectl -n crossplane-system logs provider-aws-f6a4a9bdba04-84ddf67474-z78nl | less\n\n# For Crossplane core logs\n# kubectl -n crossplane-system logs crossplane-5b6896bb4c-mjr8x | less\n
An example application was deployed as a claim of a composite resource. Kind = ExampleApp. Name = example-application.
The example application never reaches available state.
Run kubectl describe exampleapp example-application
Status:\nConditions:\n Last Transition Time: 2022-03-01T22:57:38Z\n Reason: Composite resource claim is waiting for composite resource to become Ready\n Status: False\n Type: Ready\nEvents: <none>\n
No error in events. Find its cluster scoped resource (composite resource).
In the above output, we see the cluster scoped resource for this claim. Kind = XExampleApp name = example-application-xqlsz
Get the cluster resource's event.
# kubectl describe xexampleapp example-application-xqlsz\n\nEvents:\nType Reason Age From Message\n---- ------ ---- ---- -------\nNormal PublishConnectionSecret 9s (x2 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully published connection details\nNormal SelectComposition 6s (x6 over 11s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully selected composition\nWarning ComposeResources 6s (x6 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io cannot render composed resource from resource template at index 3: cannot use dry-run create to name composed resource: an empty namespace may not be set during creation\nNormal ComposeResources 6s (x6 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully composed resources\n
We see errors in the events. It is complaining about not specifying namespace in its compositions. For this particular kind of error, we can get its sub-resources and check which one is not created.
6. Notice the last element in the array does not have a name. When a resource in composition fails validation, the resource object is not created and will not have a name. For this particular issue, we need to specify the namespace for the IRSA resource.
There are two ways to install providers in Crossplane. Using configuration.pkg.crossplane.io and provider.pkg.crossplane.io. In this repository, we use provider.pkg.crossplane.io. Note that if you define a configuration.pkg.crossplane.io object, Crossplane will create a provider.pkg.crossplane.io object. This object is managed by Crossplane. Please refer to this guide for more information about Crossplane Packages.
If you are experiencing provider issues, steps below are a good starting point.
Check the status of provider object.
# kubectl describe provider.pkg.crossplane.io provider-aws\nStatus:\n Conditions:\n Last Transition Time: 2022-08-04T16:19:44Z\n Reason: HealthyPackageRevision\n Status: True\n Type: Healthy\n Last Transition Time: 2022-08-04T16:14:29Z\n Reason: ActivePackageRevision\n Status: True\n Type: Installed\n Current Identifier: crossplane/provider-aws:v0.29.0\n Current Revision: provider-aws-a2e16ca2fc1a\nEvents:\n Type Reason Age From Message\n ---- ------ ---- ---- -------\n Normal InstallPackageRevision 9m49s (x237 over 4d17h) packages/provider.pkg.crossplane.io Successfully installed package revision\n
In the output above we see that this provider is healthy. To get more information about this provider, we can dig deeper. The Current Revision field let us know of our next object to look at.
When you create a provider object, Crossplane will create a ProviderRevision object based on the contents of the OCI image. In this example, we are specifying the OCI image to be crossplane/provider-aws:v0.29.0. This image contains a YAML file which defines many Kubernetes objects such as Deployment, ServiceAccount, and CRDs. The ProviderRevision object creates resources necessary for a provider to function based on the contents of the YAML file. To inspect what is deployed as part of the provider package, we inspect the ProviderRevision object. The Current Revision field above indicates which ProviderRevision object is currently used for this provider.
# kubectl get providerrevision provider-aws-a2e16ca2fc1a\n\nNAME HEALTHY REVISION IMAGE STATE DEP-FOUND DEP-INSTALLED AGE\nprovider-aws-a2e16ca2fc1a True 1 crossplane/provider-aws:v0.29.0 Active 19d\n
When you describe the object, you will find that many objects are managed by this same object.
# kubectl describe providerrevision provider-aws-a2e16ca2fc1a\n\nStatus:\n Controller Ref:\n Name: provider-aws-a2e16ca2fc1a\n Object Refs:\n API Version: apiextensions.k8s.io/v1\n Kind: CustomResourceDefinition\n Name: natgateways.ec2.aws.crossplane.io\n UID: 5c36d1bc-61b8-44f8-bca0-47e368af87a9\n ....\nEvents:\n Type Reason Age From Message\n ---- ------ ---- ---- -------\n Normal SyncPackage 22m (x369 over 4d18h) packages/providerrevision.pkg.crossplane.io Successfully configured package revision\n Normal BindClusterRole 15m (x348 over 4d18h) rbac/providerrevision.pkg.crossplane.io Bound system ClusterRole to provider ServiceAccount(s)\n Normal ApplyClusterRoles 15m (x364 over 4d18h) rbac/providerrevision.pkg.crossplane.io Applied RBAC ClusterRoles\n
The event field will also indicate any issues that may have occurred during this process. 3. If you do not see any errors in the event field above, you should check if deployments and pods were provisioned successfully. As a part of the provider configuration process, a deployment is created:
Compositions can be nested within a composition. Take a look at the example-application defined in the compositions/aws-provider/example-application directory. The Composition contains Compositions defined in other directories and creates a DynamoDB table, IAM policies for the table, a Kubernetes service account, and a IAM role for service accounts (IRSA). This pattern is very powerful. It let you define your abstraction based on someone else's prior work.
An example yaml file to deploy this Composition is available at examples/aws-provider/composite-resources/example-application/example-application.yaml.
Install the AWS Compositions and XRDs following the instructions in compositions/README.md
Let\u2019s take a look at how this example application can be deployed.
You can look at the example application object, but it doesn\u2019t tell you much about what is happening. Let\u2019s dig deeper.
# kubectl get exampleapp -n example-app example-application -o=jsonpath='{.spec.resourceRef}'\n{\"apiVersion\":\"awsblueprints.io/v1alpha1\",\"kind\":\"XExampleApp\",\"name\":\"example-application-8x9fr\"}\n
By looking at the spec.resourceRef field, you can see which cluster wide object this object created. Let\u2019s see what resources are created in the cluster wide object.
We see that it has five sub objects. Notice the first object is the XDynamoDBTable kind. This application Composition contains the DynamoDB table Composition. In fact, four out of five sub objects in the above output are Compositions.
Let\u2019s take a look at the XIRSA object. As the name implies, this object is responsible for setting up EKS IRSA for the application pod to use.
As you can see, it created an IAM Role and attached policies. It also created a Kubernetes service account as represented by the last element. If you look at the created service account, it has the necessary properties for IRSA to function.
# kubectl get sa -n example-app example-app -o yaml\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n annotations:\n eks.amazonaws.com/role-arn: arn:aws:iam::123456789:role/example-application-8x9fr-nwgbh\n
"},{"location":"patterns/patching-101/#from-composite-resource-to-managed-resource","title":"From Composite Resource to Managed Resource","text":"
Crossplane compositions allow you to modify sub resources based on arbitrary fields from their composite resource. This type of patches are referred as FromCompositeFieldPath. Take for an example:
This tells Crossplane to: 1. Look at the spec.region field in the Composite Resource. 2. Then copy that value into the spec.forProvider.region field in this instance of managed resource.
"},{"location":"patterns/patching-101/#from-managed-resource-to-composite-resource","title":"From Managed Resource to Composite Resource","text":"
Compositions also allow you to modify the composite resource from its sub resources. For example:
type: ToCompositeFieldPath\nfromFieldPath: status.atProvider.arn\ntoFieldPath: status.bucketArn\npolicy:\n fromFieldPath: Optional # This can be omitted since it defaults to Optional.\n
This tells Crossplane to: 1. Look at the status.atProvider.arn field on the managed resource. 2. If the status.atProvider.arn field is empty, skip this patch. 3. Copy the value into the status.bucketArn field on the composite resource.
This tells Crossplane to: 1. Look at the status.bucketArn field in the Composite Resource. 2. If the status.bucketArn field is empty, do not skip. Stop composing this managed resource. 3. Once the status.bucketArn field is filled with a value, copy that value into the spec.forProvider.bucketArn in the managed resource.
With the use of Required policy, you can create a soft dependency. This is useful when you do not want to create a resource before another resource is ready.
You can also perform modifications to values when patching. For example, you can use the following transformation to extract the accountId of this managed policy.
This tells Crossplane to: 1. Look at the status.policyArn field in the Managed Resource. 2. If the field has a value, take that value and run a regular expression match against it. 3. When there is a match, take the first capture group and store it in the status.accountId field in the Composite Resource.
See the official documentation for more information. https://docs.crossplane.io/master/concepts/composition/#patch-types
"},{"location":"patterns/rds-day-2/","title":"RDS day 2 operations","text":""},{"location":"patterns/rds-day-2/#background-and-problem-statement","title":"Background and problem statement","text":"
Managing databases can be challenging because they are stateful, not easily replaceable, and data loss could have significant business impacts. An unexpected restart could cause havoc to applications that depend on them. Because of this, database users and administrators want to offload the management, maintenance, and availability of databases to another entity such as cloud providers. Amazon RDS is one of such services. Crossplane AWS provider aims to create building blocks for self-service experience for developers by providing abilities to manage AWS resources in Kubernetes native ways.
In Amazon RDS some operations require an instance restart. For example, version upgrade and storage size modification require an instance restart. RDS attempts to minimize impact of such operations by: 1. Define a scheduled maintenance window. 2. Queue changes that you want to make. Note that these changes may not need restarts. 3. During the next scheduled maintenance window, changes are applied.
This approach is fundamentally different from GitOps. In GitOps, when a change is checked into your repository, it is expected that actual resources are to match the specifications provided in the repository.
RDS supports applying these changes immediately instead of waiting for a scheduled maintenance window, and when using Crossplane AWS providers, they have the option to apply changes immediately as well. This is the option that should be used when using RDS with GitOps. However this leads to problems when enabling self service model where developers can provision resources on their own.
There are some problems when using the apply immediately option. - Updates made to certain fields would need a restart to take effect but this information may not be surfaced back to users. For example, changing the parameter group on an instance requires a restart but this information is not available in the Upbound Official provider. The community provider surface this information in a status field. In both providers, the status fields indicates Available and ReconcileSuccess. This could give end users an illusion of successful parameter changes, but in reality it has not taken effect yet. - Some field changes triggers an instance restart. For example, changing the instance class triggers a restart and potentially cause an outage. Developers may not know which fields would cause restarts because they are not familiar with underlying technologies. You could document potentially dangerous fields, but it is not enough to reliably stop it from happening.
The main goal of this document is to provide guidance on how to provide guardrails for end users when managing RDS resources through Crossplane.
Parameter Groups define how the underlying database engine is configured. For example, if you wish to change the binlog_cache_size configuration value for your MySQL database, you can do that through parameter groups. A parameter group is not limited to be used by a single RDS instance. A parameter group can be used by multiple RDS instances.
In Parameter Groups, there are two types of parameters: dynamic and static. Dynamic parameters do not require a restart for their values to be applied to the running instance / cluster. Static parameters require a restart for their values to be applied. Additionally, dynamic parameters support specifying how changes to them are applied. When immediate is specified the changes to dynamic parameters are applied immediately. When pending-reboot is specified, the changes to dynamic parameters are applied during next restart or during the next maintenance window, whichever is earlier.
Since static parameters do not support immediate apply option, specifying this in your composition could lead to some unexpected errors. Therefore, extra care should be taken when exposing this resource to your end users. End users may not be aware of underlying engine specifications.
Summarizing everything above effectively means there are a few general approaches to managing RDS configuration changes.
You want to ensure that parameter group values in the running cluster / instance match what is defined in your Git repository with no delay. The only certain way do this is by restarting the cluster/ instance during the reconciliation process.
You can wait for parameter group changes to be applied during the next maintenance window. This means you may need to wait maximum 7 days for the changes to be applied.
The change does not have to be applied immediately but it needs to happen sooner than 7 days. This requires a separate workflow to restart cluster / instance.
Use the RDS Blue Green deployment feature.
For reference, problems encountered during parameter group updates in ACK and Terraform are discussed in this issue and this blog post.
As of writing this doc, there are 9 fields that require a restart to take effect when using a single RDS instance. There are 3 fields that require a restart when using multi-AZ instances. Unfortunately there is no native way to get these fields programmatically.
There are 188 static parameters in mysql8.0 family, and similar number of them are in other parameter group families as well. You can get a list of static parameters by using the aws rds describe-engine-default-parameters command.
These fields and parameters need to be stored for use by whatever check mechanism you choose, and they need to be updated regularly.
It is also worth pointing out that when a user updates a parameter in a parameter group, the changes to parameter groups themselves usually work without problems. However, it is often not the intention of these changes. The intention of changes is to change the parameter and apply it to a running instance. In both providers, changes to static parameters are not actually applied until the next maintenance window or a manual restart is issued.
We will discuss a few approaches to this problem below. Whichever approach you choose, it is important for the check mechanisms to work reliably. It's easy to lose users' trust when checks say there will be a restart but no restart happened. Or worse, checks did not detect potential restarts and caused an outage.
"},{"location":"patterns/rds-day-2/#check-during-pr","title":"Check during PR","text":"
Use Pull Request as a checkpoint and ensure developers are aware of potential consequences of the changes. An example process may look something like the following.
flowchart TD\n Comment(Comment on PR)\n\n subgraph Workflow\n GetChangedFiles(Get changed files)\n GetChangedFiles(Get changed files)\n StepCheck(Will this cause a restart?)\n end\n\n subgraph Data Source \n FieldDefinitions(Fields that need restarting)\n end \n\n Restart(Restart immediately)\n\n FieldDefinitions <--reference--> StepCheck\n\n PR(PR Created) --trigger--> GetChangedFiles --> StepCheck --yes--> Comment --> Approval(Wait for Approval) --> Merge --> GitOps(GitOps Tooling)\n StepCheck--no--> Approval\n GitOps --apply changes now --> Restart --> Done\n GitOps --wait until next \\n maintenance window--> Done\n
In this example, whenever a pull request is created, a workflow is executed and a comment is created on the PR warning the developers of potential impacts. When developers approve the PR, it implies that they are aware of consequences. To check if a PR is impacted, you can use of the following options: - Parse git diff and search for changes to \"dangerous\" fields - Use kubectl diff then look for changes to \"dangerous\" fields. This requires read access to the target cluster but more accurate.
"},{"location":"patterns/rds-day-2/#check-at-runtime","title":"Check at runtime","text":"
Another approach is to deny such operation at runtime using a policy engine and/or custom validating web hook unless certain conditions are met. This means problems with RDS configuration is communicated to the developers through their GitOps tooling by providing reasons for denial. Note that it is a good idea to check at runtime even if you have a check during PR.
In the example above, no check is performed during PR. During admission into the Kubernetes cluster, a validating controller will reach out to the ticketing system and verify if this change is approved. If no ticket associated with this change is approved, it's rejected with provided reason.
Note that ticketing system here is just an example. It can be any type of systems that provides a decision.
flowchart LR\n subgraph Kubernetes\n ConfigMap(ConfigMap w/ ticket numbers)\n ValidatingController(Policy Engine / Validating controller)\n end \n\n subgraph Git\n subgraph PR\n Claim\n Manifests(Other Manifests)\n end\n end\n\n subgraph Ticketing\n Approved(Approved Changes)\n end\n\n User\n GitOps(GitOps tooling)\n\n User --Create Ticket--> Ticketing\n User --Annoate with ticket number--> Claim\n PR(PR Merged) --> GitOps --> ValidatingController\n ValidatingController --reference--> ConfigMap\n ValidatingController --deny if not approved \\n and provide reason--> GitOps\n Approved --create when the ticket \\n is approved--> ConfigMap\n ValidatingController--Once Approved--> Restart\n
In this example, developer creates a ticket in the ticketing system and annotates the infrastructure claim with the ticket number. The admission controller checks if the change affects fields that require approval. If approval is required, the change is denied until the ticket is approved and the reason is given back to the GitOps tooling.
Once the ticket is approved, a config map is created with the ticket number as its name or as one of annotations. Next time the GitOps tooling attempts to apply manifests, the admission controller sees the ConfigMap is now created and allows it to be deployed. Once it is deployed, the ConfigMap can be marked for deletion. In this approach, there is no need for read access to the ticketing system.
"},{"location":"patterns/rds-day-2/#blue-green-deployment","title":"Blue Green deployment","text":"
RDS added native support for blue green deployment. This allows for safer database updates because RDS manages the process of creating an alternate instance, copying data over to it, and shifting traffic to it.
As of writing this doc, neither providers support this functionality. Because the functionality is available in Terraform, the Upbound official provider should be able to support this in the future. In addition, this functionality is supported for MariaDB and MySQL only.
In case of an emergency where something unexpected ocurred and you need to stop providers from making changes to AWS resources, you can use one of the following methods: - To prevent providers from making changes to a specific resource, you can use the crossplane.io/paused annotation. e.g.
- To prevent providers from making changes to ALL of your resources, you can update the number of replicas in ControllerConfig to 0. This will terminate the running pod. e.g.
apiVersion: pkg.crossplane.io/v1alpha1\nkind: ControllerConfig\nspec:\n replicas: 0 # This value is usually 1. \n
- If you cannot access the cluster, you can prevent providers from making changes to all or some of your resources by either removing the policy associated with the IAM role or adjusting the policy to allow it to make changes to certain resources only."},{"location":"patterns/rds-day-2/#references","title":"References","text":"
In this doc, we will configure the following: - A Vault server (in-cluster or outside cluster) - A Crossplane installation with AWS provider on EKS - Provision a S3 bucket through Crossplane. - Publish bucket information as a Vault secret. - Access the published information in Vault from a pod using Vault Agent Injector
Following command line tools: - kubectl - helm - eksctl - aws
Note: - As of Crossplane 1.9.0, the support for external secret store is still in alpha state and may go under changes. - This assumes a use case for single-cluster multi-tenant. However, the underlying concepts discussed here should be applicable to multi-cluster setup as well. - This doc is based on the excellent external vault configuration guide. Please check these guides out for more detailed information.
"},{"location":"patterns/vault-integration/#procedure","title":"Procedure","text":""},{"location":"patterns/vault-integration/#provision-a-eks-cluster","title":"Provision a EKS cluster","text":"
# from this repository root\neksctl create cluster -f bootstrap/eksctl/eksctl.yaml\n
"},{"location":"patterns/vault-integration/#create-a-vault-service","title":"Create a Vault service","text":"
You can create a vault service in the same cluster as Crossplane or create a service on a VM.
"},{"location":"patterns/vault-integration/#on-an-external-vm","title":"On an external VM","text":"
This VM must be reachable by the Crossplane installation. If you are using an EC2 instance, routing, network ACL, and Security Groups must be configured to allow for traffic from Crossplane pod to the VM.
Commands below assumes the VM is an Ubuntu instance.
sudo systemctl enable vault.service\n\n# create a configuration file for vault. NOTE: this creates a vault service with TLS disabled. \n# This is done to make the configuration step easy to follow only. TLS should be enabled for real workloads.\ncat <<< 'ui = true\n\nstorage \"file\" {\n path = \"/opt/vault/data\"\n}\n\nlistener \"tcp\" {\n address = \"0.0.0.0:8200\"\n tls_disable = 1\n}' | sudo -u vault tee /etc/vault.d/vault.hcl > /dev/null\n\nsudo systemctl start vault.service\n\nexport VAULT_ADDR='http://127.0.0.1:8200'\n# This command will print out unseal keys and the root token.\nvault operator init\nvault operator unseal # do this three times. each time with a different unseal key.\nvault secrets enable -path=secret kv-v2\nvault auth enable kubernetes\n
Get the IP address of this instance. For an EC2 instance, it should be the private IP of the instance. For a simple EC2 instance:
Rut the following commands from a place where you have access to your Kubernetes cluster, e.g. your laptop. The Vault Agent Sidecar injector looks for CREATE and UPDATE events, then it will inject vault secret into the containers.
kubectl create ns vault-system\n# install vault injector. be sure to use the IP address obtained above.\nhelm -n vault-system install vault hashicorp/vault \\\n --set \"injector.externalVaultAddr=http://<PRIVATE_IP_ADDRESS>:8200\"\n\nTOKEN_REVIEW_JWJ=$(kubectl -n vault-system get secret $(kubectl -n vault-system get secrets --output=json | jq -r '.items[].metadata | select(.name|startswith(\"vault-token-\")).name') --output='go-template={{ .data.token }}' | base64 --decode)\nKUBE_HOST=$(kubectl config view --raw --minify --flatten --output='jsonpath={.clusters[].cluster.server}')\nKUBE_CA_CERT=$(kubectl config view --raw --minify --flatten --output='jsonpath={.clusters[].cluster.certificate-authority-data}' | base64 --decode)\nISSUER=$(kubectl get --raw /.well-known/openid-configuration | jq -r .issuer)\n
Configure Kubernetes authentication, policy, and role for Crossplane to use in your VM:
Once Crossplane is installed, install its AWS provider.
Update the AWS provider YAML file with your role ARN, then execute the following commands.
kubectl apply -f bootstrap/eksctl/crossplane/aws-provider-vault-secret.yaml\nkubectl get ProviderRevision\n# example output\n# NAME HEALTHY REVISION IMAGE STATE DEP-FOUND DEP-INSTALLED AGE\n# provider-aws-a2e16ca2fc1a True 1 crossplane/provider-aws:v0.29.0 Active 23s\n
StoreConfig objects provides Crossplane and its providers information about how to connect to secret stores. These objects must be configured for external secret integrations to work.
Update the store config YAML file with your endpoint information. If you configured vault outside of the cluster, it should be the private IP address. e.g. 10.0.0.1:8200
This creates two configurations for secrets stores: - A configuration named in-cluster for Crossplane (compositions). This tells Crossplane to store composition secrets in the same cluster as Kubernetes secrets. - Another configuration named vault for AWS provider. This tells the provider to store secrets the vault instance under the /secret/crossplane-system namespace. To access the vault instance, a token is created by the sidecar at /vault/secrets/token.
The composition that is of interest is compositions/aws-provider/s3/multi-tenant.yaml. This composition demonstrates the following: - ProviderConfig selection based on the claim's namespace. - Publishes bucket information to Kubernetes secrets and Vault. - Published Vault secrets are created under the claim's namespace in Vault.
kubectl describe bucket\n# example events\n# Events:\n# Type Reason Age From Message\n# ---- ------ ---- ---- -------\n# Warning CannotConnectToProvider 1s (x5 over 14s) managed/bucket.s3.aws.crossplane.io cannot get referenced Provider: ProviderConfig.aws.crossplane.io \"default-provider-config\" not found\n
In the claim file, we specify a provider config name. However, this is patched out to use the provider config with name <NAMESPACE>-provider-config. This is why the error message indicates provider config with name default-provider-config is not found.
Since we created a provider config named application1-provider-config, we should be able to create a claim in namespace called application1.
#create namespace\nkubectl create ns application1 || true\n# create in new namespace\nkubectl apply -n application1 -f examples/aws-provider/composite-resources/s3/multi-tenant.yaml\n\nkubectl -n application1 get objectstorage\n# NAME READY CONNECTION-SECRET AGE\n# standard-object-storage True 22s\n
Once the claim reaches the ready state, you should be able to verify. Secret creation:
kubectl -n crossplane-system get secret `kubectl get xobjectstorage -o json | jq -r '.items[0].metadata.uid'` -o go-template='{{range $k,$v := .data}}{{printf \"%s: \" $k}}{{if not $v}}{{$v}}{{else}}{{$v | base64decode}}{{end}}{{\"\\n\"}}{{end}}'\n# example output\n# bucket-name: standard-object-storage-qlgvz-hz2dn\n# region: us-west-2\n
The same information should be available in Vault:
# in your vault installation\nvault kv get secret/crossplane-system/application1/dev/bucket\n# ==================== Secret Path ====================\n# secret/data/crossplane-system/application1/dev/bucket\n#\n# ======= Metadata =======\n# Key Value\n# --- -----\n# created_time 2022-07-22T20:51:27.852598176Z\n# custom_metadata map[awsblueprints.io/composition-name:s3bucket-multi-tenant.awsblueprints.io awsblueprints.io/environment:dev awsblueprints.io/provider:aws secret.crossplane.io/owner-uid:0c601153-358d-45e1-8e0a-0f34991bed82]\n# deletion_time n/a\n# destroyed false\n# version 1\n#\n# ====== Data ======\n# Key Value\n# --- -----\n# endpoint standard-object-storage-4p2wr-lxb74\n# region us-west-2\n
This is because the pod is created in the default namespace and the Vault policy we configured earlier does not allow it to access secrets in another namespace.
"}]}
\ No newline at end of file
+{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Overview","text":""},{"location":"#blueprints-for-crossplane-on-amazon-eks","title":"Blueprints for Crossplane on Amazon EKS","text":"
Note: AWS Blueprints for Crossplane on Amazon Elastic Kubernetes Service is under active development and should be considered a pre-production framework.
AWS Crossplane Blueprints is an open source repo to bootstrap Amazon Elastic Kubernetes Service Clusters. and provision AWS resources with a library of Crossplane Compositions (XRs) with Composite Resource Definitions (XRDs).
If you are new to Crossplane, it is highly recommended to get yourself familiarized with Crossplane concepts. The official documentation and this blog post are good starting points.
Compositions in this repository enable platform teams to define and offer bespoke AWS infrastructure APIs to the teams of application developers based on predefined Composite Resources (XRs), encompassing one or more of AWS Managed Resources (MRs)
This repo provides multiple options to bootstrap Amazon EKS Clusters with Crossplane and AWS Providers. Checkout the following README for full deployment configuration
Bootstrap EKS Cluster with eksctl
Bootstrap EKS Cluster with Terraform
\u2705 Configure the EKS cluster
Enable IRSA support for your EKS cluster for the necessary permissions to spin up other AWS services. Depending on the provider, refer to the bootstrap README for this configuration.
AWS Provider - Crossplane Compositions for AWS Services
Upbound AWS Provider - Upbound Crossplane Compositions for AWS Services
\u2705 Deploy the Examples
With the setup complete, you can then follow instructions on deploying crossplane compositions or managed resources you want to experiment with. Keep in mind that the list of compositions and managed resources in this repository are evolving.
Deploy the Examples by following this README
\u2705 Work with nested compositions.
Compositions can be nested to further define and abstract application specific needs.
Take a quick tour of a nested composition example
\u2705 Work with external secrets.
Crossplane can be configured to publish secrets external to the cluster in which it runs.
Try it out with this guide
\u2705 Check out the RDS day 2 operation doc
\u2705 Checkout example Gatekeeper configurations.
This library is licensed under the Apache 2.0 License.
"},{"location":"faq/","title":"Frequently Asked Questions","text":""},{"location":"faq/#timeouts-on-destroy","title":"Timeouts on destroy","text":"
Customers who are deleting their environments using terraform destroy may see timeout errors when VPCs are being deleted. This is due to a known issue in the vpc-cni
Customers may face a situation where ENIs that were attached to EKS managed nodes (same may apply to self-managed nodes) are not being deleted by the VPC CNI as expected which leads to IaC tool failures, such as:
ENIs are left on subnets
EKS managed security group which is attached to the ENI can\u2019t be deleted by EKS
The current recommendation is to execute cleanup in the following order:
delete all pods that have been created in the cluster.
For consuming Crossplane Blueprints, please see the Getting Started section. For exploring and trying out the patterns provided, please clone the project locally to quickly get up and running with a pattern. After cloning the project locally, cd into the pattern directory of your choice.
To provision the pattern, the typical steps of execution are as follows:
Once all of the resources have successfully been provisioned, the following command can be used to update the kubeconfig on your local machine and allow you to interact with your EKS Cluster using kubectl.
The examples will output the aws eks update-kubeconfig ... command as part of the Terraform apply output to simplify this process for users
Once you have updated your kubeconfig, you can verify that you are able to interact with your cluster by running the following command:
kubectl get nodes\n
This should return a list of the node(s) running in the cluster created. If any errors are encountered, please re-trace the steps above and consult the pattern's README.md for more details on any additional/specific steps that may be required.
Some resources may have been created that Terraform is not aware of that will cause issues when attempting to clean up the pattern. Please see the destroy.md for more details.
"},{"location":"patterns/debugging/","title":"Debugging CompositeResourceDefinitions (XRD) and Compositions","text":""},{"location":"patterns/debugging/#composite-resources-and-claim-overview","title":"Composite resources and claim overview","text":"
Most error messages are logged to resources' event field. Whenever your Composite Resources are not getting provisioned, follow the following: 1. Get the events for the root resource using kubectl describe or kubectl get event 2. If there are errors in the events, address them. 3. If no errors, follow its sub-resources. kubectl get <KIND> <NAME> -o=jsonpath='{.spec.resourceRef}{\" \"}{.spec.resourceRefs}' | jq 4. Go back to step 1 using one of resources returned by step 3.
Note: Debugging is also enabled for the AWS provider pods. You may find it useful to check the logs for the provider pods for extra information on failures. You can also disable logging here.
# kubectl get pods -n crossplane-system\nNAME READY STATUS RESTARTS AGE\ncrossplane-5b6896bb4c-mjr8x 1/1 Running 0 12d\ncrossplane-rbac-manager-7874897d59-fc9wf 1/1 Running 0 12d\nprovider-aws-f6a4a9bdba04-84ddf67474-z78nl 1/1 Running 0 12d\nprovider-kubernetes-cfae2275d58e-6b7bcf5bb5-2rjk2 1/1 Running 0 8d\n\n# For the AWS provider logs\n# kubectl -n crossplane-system logs provider-aws-f6a4a9bdba04-84ddf67474-z78nl | less\n\n# For Crossplane core logs\n# kubectl -n crossplane-system logs crossplane-5b6896bb4c-mjr8x | less\n
An example application was deployed as a claim of a composite resource. Kind = ExampleApp. Name = example-application.
The example application never reaches available state.
Run kubectl describe exampleapp example-application
Status:\nConditions:\n Last Transition Time: 2022-03-01T22:57:38Z\n Reason: Composite resource claim is waiting for composite resource to become Ready\n Status: False\n Type: Ready\nEvents: <none>\n
No error in events. Find its cluster scoped resource (composite resource).
In the above output, we see the cluster scoped resource for this claim. Kind = XExampleApp name = example-application-xqlsz
Get the cluster resource's event.
# kubectl describe xexampleapp example-application-xqlsz\n\nEvents:\nType Reason Age From Message\n---- ------ ---- ---- -------\nNormal PublishConnectionSecret 9s (x2 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully published connection details\nNormal SelectComposition 6s (x6 over 11s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully selected composition\nWarning ComposeResources 6s (x6 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io cannot render composed resource from resource template at index 3: cannot use dry-run create to name composed resource: an empty namespace may not be set during creation\nNormal ComposeResources 6s (x6 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully composed resources\n
We see errors in the events. It is complaining about not specifying namespace in its compositions. For this particular kind of error, we can get its sub-resources and check which one is not created.
6. Notice the last element in the array does not have a name. When a resource in composition fails validation, the resource object is not created and will not have a name. For this particular issue, we need to specify the namespace for the IRSA resource.
There are two ways to install providers in Crossplane. Using configuration.pkg.crossplane.io and provider.pkg.crossplane.io. In this repository, we use provider.pkg.crossplane.io. Note that if you define a configuration.pkg.crossplane.io object, Crossplane will create a provider.pkg.crossplane.io object. This object is managed by Crossplane. Please refer to this guide for more information about Crossplane Packages.
If you are experiencing provider issues, steps below are a good starting point.
Check the status of provider object.
# kubectl describe provider.pkg.crossplane.io provider-aws\nStatus:\n Conditions:\n Last Transition Time: 2022-08-04T16:19:44Z\n Reason: HealthyPackageRevision\n Status: True\n Type: Healthy\n Last Transition Time: 2022-08-04T16:14:29Z\n Reason: ActivePackageRevision\n Status: True\n Type: Installed\n Current Identifier: crossplane/provider-aws:v0.29.0\n Current Revision: provider-aws-a2e16ca2fc1a\nEvents:\n Type Reason Age From Message\n ---- ------ ---- ---- -------\n Normal InstallPackageRevision 9m49s (x237 over 4d17h) packages/provider.pkg.crossplane.io Successfully installed package revision\n
In the output above we see that this provider is healthy. To get more information about this provider, we can dig deeper. The Current Revision field let us know of our next object to look at.
When you create a provider object, Crossplane will create a ProviderRevision object based on the contents of the OCI image. In this example, we are specifying the OCI image to be crossplane/provider-aws:v0.29.0. This image contains a YAML file which defines many Kubernetes objects such as Deployment, ServiceAccount, and CRDs. The ProviderRevision object creates resources necessary for a provider to function based on the contents of the YAML file. To inspect what is deployed as part of the provider package, we inspect the ProviderRevision object. The Current Revision field above indicates which ProviderRevision object is currently used for this provider.
# kubectl get providerrevision provider-aws-a2e16ca2fc1a\n\nNAME HEALTHY REVISION IMAGE STATE DEP-FOUND DEP-INSTALLED AGE\nprovider-aws-a2e16ca2fc1a True 1 crossplane/provider-aws:v0.29.0 Active 19d\n
When you describe the object, you will find that many objects are managed by this same object.
# kubectl describe providerrevision provider-aws-a2e16ca2fc1a\n\nStatus:\n Controller Ref:\n Name: provider-aws-a2e16ca2fc1a\n Object Refs:\n API Version: apiextensions.k8s.io/v1\n Kind: CustomResourceDefinition\n Name: natgateways.ec2.aws.crossplane.io\n UID: 5c36d1bc-61b8-44f8-bca0-47e368af87a9\n ....\nEvents:\n Type Reason Age From Message\n ---- ------ ---- ---- -------\n Normal SyncPackage 22m (x369 over 4d18h) packages/providerrevision.pkg.crossplane.io Successfully configured package revision\n Normal BindClusterRole 15m (x348 over 4d18h) rbac/providerrevision.pkg.crossplane.io Bound system ClusterRole to provider ServiceAccount(s)\n Normal ApplyClusterRoles 15m (x364 over 4d18h) rbac/providerrevision.pkg.crossplane.io Applied RBAC ClusterRoles\n
The event field will also indicate any issues that may have occurred during this process. 3. If you do not see any errors in the event field above, you should check if deployments and pods were provisioned successfully. As a part of the provider configuration process, a deployment is created:
Compositions can be nested within a composition. Take a look at the example-application defined in the compositions/aws-provider/example-application directory. The Composition contains Compositions defined in other directories and creates a DynamoDB table, IAM policies for the table, a Kubernetes service account, and a IAM role for service accounts (IRSA). This pattern is very powerful. It let you define your abstraction based on someone else's prior work.
An example yaml file to deploy this Composition is available at examples/aws-provider/composite-resources/example-application/example-application.yaml.
Install the AWS Compositions and XRDs following the instructions in compositions/README.md
Let\u2019s take a look at how this example application can be deployed.
You can look at the example application object, but it doesn\u2019t tell you much about what is happening. Let\u2019s dig deeper.
# kubectl get exampleapp -n example-app example-application -o=jsonpath='{.spec.resourceRef}'\n{\"apiVersion\":\"awsblueprints.io/v1alpha1\",\"kind\":\"XExampleApp\",\"name\":\"example-application-8x9fr\"}\n
By looking at the spec.resourceRef field, you can see which cluster wide object this object created. Let\u2019s see what resources are created in the cluster wide object.
We see that it has five sub objects. Notice the first object is the XDynamoDBTable kind. This application Composition contains the DynamoDB table Composition. In fact, four out of five sub objects in the above output are Compositions.
Let\u2019s take a look at the XIRSA object. As the name implies, this object is responsible for setting up EKS IRSA for the application pod to use.
As you can see, it created an IAM Role and attached policies. It also created a Kubernetes service account as represented by the last element. If you look at the created service account, it has the necessary properties for IRSA to function.
# kubectl get sa -n example-app example-app -o yaml\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n annotations:\n eks.amazonaws.com/role-arn: arn:aws:iam::123456789:role/example-application-8x9fr-nwgbh\n
"},{"location":"patterns/patching-101/#from-composite-resource-to-managed-resource","title":"From Composite Resource to Managed Resource","text":"
Crossplane compositions allow you to modify sub resources based on arbitrary fields from their composite resource. This type of patches are referred as FromCompositeFieldPath. Take for an example:
This tells Crossplane to: 1. Look at the spec.region field in the Composite Resource. 2. Then copy that value into the spec.forProvider.region field in this instance of managed resource.
"},{"location":"patterns/patching-101/#from-managed-resource-to-composite-resource","title":"From Managed Resource to Composite Resource","text":"
Compositions also allow you to modify the composite resource from its sub resources. For example:
type: ToCompositeFieldPath\nfromFieldPath: status.atProvider.arn\ntoFieldPath: status.bucketArn\npolicy:\n fromFieldPath: Optional # This can be omitted since it defaults to Optional.\n
This tells Crossplane to: 1. Look at the status.atProvider.arn field on the managed resource. 2. If the status.atProvider.arn field is empty, skip this patch. 3. Copy the value into the status.bucketArn field on the composite resource.
This tells Crossplane to: 1. Look at the status.bucketArn field in the Composite Resource. 2. If the status.bucketArn field is empty, do not skip. Stop composing this managed resource. 3. Once the status.bucketArn field is filled with a value, copy that value into the spec.forProvider.bucketArn in the managed resource.
With the use of Required policy, you can create a soft dependency. This is useful when you do not want to create a resource before another resource is ready.
You can also perform modifications to values when patching. For example, you can use the following transformation to extract the accountId of this managed policy.
This tells Crossplane to: 1. Look at the status.policyArn field in the Managed Resource. 2. If the field has a value, take that value and run a regular expression match against it. 3. When there is a match, take the first capture group and store it in the status.accountId field in the Composite Resource.
See the official documentation for more information. https://docs.crossplane.io/master/concepts/composition/#patch-types
"},{"location":"patterns/rds-day-2/","title":"RDS day 2 operations","text":""},{"location":"patterns/rds-day-2/#background-and-problem-statement","title":"Background and problem statement","text":"
Managing databases can be challenging because they are stateful, not easily replaceable, and data loss could have significant business impacts. An unexpected restart could cause havoc to applications that depend on them. Because of this, database users and administrators want to offload the management, maintenance, and availability of databases to another entity such as cloud providers. Amazon RDS is one of such services. Crossplane AWS provider aims to create building blocks for self-service experience for developers by providing abilities to manage AWS resources in Kubernetes native ways.
In Amazon RDS some operations require an instance restart. For example, version upgrade and storage size modification require an instance restart. RDS attempts to minimize impact of such operations by: 1. Define a scheduled maintenance window. 2. Queue changes that you want to make. Note that these changes may not need restarts. 3. During the next scheduled maintenance window, changes are applied.
This approach is fundamentally different from GitOps. In GitOps, when a change is checked into your repository, it is expected that actual resources are to match the specifications provided in the repository.
RDS supports applying these changes immediately instead of waiting for a scheduled maintenance window, and when using Crossplane AWS providers, they have the option to apply changes immediately as well. This is the option that should be used when using RDS with GitOps. However this leads to problems when enabling self service model where developers can provision resources on their own.
There are some problems when using the apply immediately option. - Updates made to certain fields would need a restart to take effect but this information may not be surfaced back to users. For example, changing the parameter group on an instance requires a restart but this information is not available in the Upbound Official provider. The community provider surface this information in a status field. In both providers, the status fields indicates Available and ReconcileSuccess. This could give end users an illusion of successful parameter changes, but in reality it has not taken effect yet. - Some field changes triggers an instance restart. For example, changing the instance class triggers a restart and potentially cause an outage. Developers may not know which fields would cause restarts because they are not familiar with underlying technologies. You could document potentially dangerous fields, but it is not enough to reliably stop it from happening.
The main goal of this document is to provide guidance on how to provide guardrails for end users when managing RDS resources through Crossplane.
Parameter Groups define how the underlying database engine is configured. For example, if you wish to change the binlog_cache_size configuration value for your MySQL database, you can do that through parameter groups. A parameter group is not limited to be used by a single RDS instance. A parameter group can be used by multiple RDS instances.
In Parameter Groups, there are two types of parameters: dynamic and static. Dynamic parameters do not require a restart for their values to be applied to the running instance / cluster. Static parameters require a restart for their values to be applied. Additionally, dynamic parameters support specifying how changes to them are applied. When immediate is specified the changes to dynamic parameters are applied immediately. When pending-reboot is specified, the changes to dynamic parameters are applied during next restart or during the next maintenance window, whichever is earlier.
Since static parameters do not support immediate apply option, specifying this in your composition could lead to some unexpected errors. Therefore, extra care should be taken when exposing this resource to your end users. End users may not be aware of underlying engine specifications.
Summarizing everything above effectively means there are a few general approaches to managing RDS configuration changes.
You want to ensure that parameter group values in the running cluster / instance match what is defined in your Git repository with no delay. The only certain way do this is by restarting the cluster/ instance during the reconciliation process.
You can wait for parameter group changes to be applied during the next maintenance window. This means you may need to wait maximum 7 days for the changes to be applied.
The change does not have to be applied immediately but it needs to happen sooner than 7 days. This requires a separate workflow to restart cluster / instance.
Use the RDS Blue Green deployment feature.
For reference, problems encountered during parameter group updates in ACK and Terraform are discussed in this issue and this blog post.
As of writing this doc, there are 9 fields that require a restart to take effect when using a single RDS instance. There are 3 fields that require a restart when using multi-AZ instances. Unfortunately there is no native way to get these fields programmatically.
There are 188 static parameters in mysql8.0 family, and similar number of them are in other parameter group families as well. You can get a list of static parameters by using the aws rds describe-engine-default-parameters command.
These fields and parameters need to be stored for use by whatever check mechanism you choose, and they need to be updated regularly.
It is also worth pointing out that when a user updates a parameter in a parameter group, the changes to parameter groups themselves usually work without problems. However, it is often not the intention of these changes. The intention of changes is to change the parameter and apply it to a running instance. In both providers, changes to static parameters are not actually applied until the next maintenance window or a manual restart is issued.
We will discuss a few approaches to this problem below. Whichever approach you choose, it is important for the check mechanisms to work reliably. It's easy to lose users' trust when checks say there will be a restart but no restart happened. Or worse, checks did not detect potential restarts and caused an outage.
"},{"location":"patterns/rds-day-2/#check-during-pr","title":"Check during PR","text":"
Use Pull Request as a checkpoint and ensure developers are aware of potential consequences of the changes. An example process may look something like the following.
flowchart TD\n Comment(Comment on PR)\n\n subgraph Workflow\n GetChangedFiles(Get changed files)\n GetChangedFiles(Get changed files)\n StepCheck(Will this cause a restart?)\n end\n\n subgraph Data Source \n FieldDefinitions(Fields that need restarting)\n end \n\n Restart(Restart immediately)\n\n FieldDefinitions <--reference--> StepCheck\n\n PR(PR Created) --trigger--> GetChangedFiles --> StepCheck --yes--> Comment --> Approval(Wait for Approval) --> Merge --> GitOps(GitOps Tooling)\n StepCheck--no--> Approval\n GitOps --apply changes now --> Restart --> Done\n GitOps --wait until next \\n maintenance window--> Done\n
In this example, whenever a pull request is created, a workflow is executed and a comment is created on the PR warning the developers of potential impacts. When developers approve the PR, it implies that they are aware of consequences. To check if a PR is impacted, you can use of the following options: - Parse git diff and search for changes to \"dangerous\" fields - Use kubectl diff then look for changes to \"dangerous\" fields. This requires read access to the target cluster but more accurate.
"},{"location":"patterns/rds-day-2/#check-at-runtime","title":"Check at runtime","text":"
Another approach is to deny such operation at runtime using a policy engine and/or custom validating web hook unless certain conditions are met. This means problems with RDS configuration is communicated to the developers through their GitOps tooling by providing reasons for denial. Note that it is a good idea to check at runtime even if you have a check during PR.
In the example above, no check is performed during PR. During admission into the Kubernetes cluster, a validating controller will reach out to the ticketing system and verify if this change is approved. If no ticket associated with this change is approved, it's rejected with provided reason.
Note that ticketing system here is just an example. It can be any type of systems that provides a decision.
flowchart LR\n subgraph Kubernetes\n ConfigMap(ConfigMap w/ ticket numbers)\n ValidatingController(Policy Engine / Validating controller)\n end \n\n subgraph Git\n subgraph PR\n Claim\n Manifests(Other Manifests)\n end\n end\n\n subgraph Ticketing\n Approved(Approved Changes)\n end\n\n User\n GitOps(GitOps tooling)\n\n User --Create Ticket--> Ticketing\n User --Annoate with ticket number--> Claim\n PR(PR Merged) --> GitOps --> ValidatingController\n ValidatingController --reference--> ConfigMap\n ValidatingController --deny if not approved \\n and provide reason--> GitOps\n Approved --create when the ticket \\n is approved--> ConfigMap\n ValidatingController--Once Approved--> Restart\n
In this example, developer creates a ticket in the ticketing system and annotates the infrastructure claim with the ticket number. The admission controller checks if the change affects fields that require approval. If approval is required, the change is denied until the ticket is approved and the reason is given back to the GitOps tooling.
Once the ticket is approved, a config map is created with the ticket number as its name or as one of annotations. Next time the GitOps tooling attempts to apply manifests, the admission controller sees the ConfigMap is now created and allows it to be deployed. Once it is deployed, the ConfigMap can be marked for deletion. In this approach, there is no need for read access to the ticketing system.
"},{"location":"patterns/rds-day-2/#blue-green-deployment","title":"Blue Green deployment","text":"
RDS added native support for blue green deployment. This allows for safer database updates because RDS manages the process of creating an alternate instance, copying data over to it, and shifting traffic to it.
As of writing this doc, neither providers support this functionality. Because the functionality is available in Terraform, the Upbound official provider should be able to support this in the future. In addition, this functionality is supported for MariaDB and MySQL only.
In case of an emergency where something unexpected ocurred and you need to stop providers from making changes to AWS resources, you can use one of the following methods: - To prevent providers from making changes to a specific resource, you can use the crossplane.io/paused annotation. e.g.
- To prevent providers from making changes to ALL of your resources, you can update the number of replicas in ControllerConfig to 0. This will terminate the running pod. e.g.
apiVersion: pkg.crossplane.io/v1alpha1\nkind: ControllerConfig\nspec:\n replicas: 0 # This value is usually 1. \n
- If you cannot access the cluster, you can prevent providers from making changes to all or some of your resources by either removing the policy associated with the IAM role or adjusting the policy to allow it to make changes to certain resources only."},{"location":"patterns/rds-day-2/#references","title":"References","text":"
In this doc, we will configure the following: - A Vault server (in-cluster or outside cluster) - A Crossplane installation with AWS provider on EKS - Provision a S3 bucket through Crossplane. - Publish bucket information as a Vault secret. - Access the published information in Vault from a pod using Vault Agent Injector
Following command line tools: - kubectl - helm - eksctl - aws
Note: - As of Crossplane 1.9.0, the support for external secret store is still in alpha state and may go under changes. - This assumes a use case for single-cluster multi-tenant. However, the underlying concepts discussed here should be applicable to multi-cluster setup as well. - This doc is based on the excellent external vault configuration guide. Please check these guides out for more detailed information.
"},{"location":"patterns/vault-integration/#procedure","title":"Procedure","text":""},{"location":"patterns/vault-integration/#provision-a-eks-cluster","title":"Provision a EKS cluster","text":"
# from this repository root\neksctl create cluster -f bootstrap/eksctl/eksctl.yaml\n
"},{"location":"patterns/vault-integration/#create-a-vault-service","title":"Create a Vault service","text":"
You can create a vault service in the same cluster as Crossplane or create a service on a VM.
"},{"location":"patterns/vault-integration/#on-an-external-vm","title":"On an external VM","text":"
This VM must be reachable by the Crossplane installation. If you are using an EC2 instance, routing, network ACL, and Security Groups must be configured to allow for traffic from Crossplane pod to the VM.
Commands below assumes the VM is an Ubuntu instance.
sudo systemctl enable vault.service\n\n# create a configuration file for vault. NOTE: this creates a vault service with TLS disabled. \n# This is done to make the configuration step easy to follow only. TLS should be enabled for real workloads.\ncat <<< 'ui = true\n\nstorage \"file\" {\n path = \"/opt/vault/data\"\n}\n\nlistener \"tcp\" {\n address = \"0.0.0.0:8200\"\n tls_disable = 1\n}' | sudo -u vault tee /etc/vault.d/vault.hcl > /dev/null\n\nsudo systemctl start vault.service\n\nexport VAULT_ADDR='http://127.0.0.1:8200'\n# This command will print out unseal keys and the root token.\nvault operator init\nvault operator unseal # do this three times. each time with a different unseal key.\nvault secrets enable -path=secret kv-v2\nvault auth enable kubernetes\n
Get the IP address of this instance. For an EC2 instance, it should be the private IP of the instance. For a simple EC2 instance:
Rut the following commands from a place where you have access to your Kubernetes cluster, e.g. your laptop. The Vault Agent Sidecar injector looks for CREATE and UPDATE events, then it will inject vault secret into the containers.
kubectl create ns vault-system\n# install vault injector. be sure to use the IP address obtained above.\nhelm -n vault-system install vault hashicorp/vault \\\n --set \"injector.externalVaultAddr=http://<PRIVATE_IP_ADDRESS>:8200\"\n\nTOKEN_REVIEW_JWJ=$(kubectl -n vault-system get secret $(kubectl -n vault-system get secrets --output=json | jq -r '.items[].metadata | select(.name|startswith(\"vault-token-\")).name') --output='go-template={{ .data.token }}' | base64 --decode)\nKUBE_HOST=$(kubectl config view --raw --minify --flatten --output='jsonpath={.clusters[].cluster.server}')\nKUBE_CA_CERT=$(kubectl config view --raw --minify --flatten --output='jsonpath={.clusters[].cluster.certificate-authority-data}' | base64 --decode)\nISSUER=$(kubectl get --raw /.well-known/openid-configuration | jq -r .issuer)\n
Configure Kubernetes authentication, policy, and role for Crossplane to use in your VM:
Once Crossplane is installed, install its AWS provider.
Update the AWS provider YAML file with your role ARN, then execute the following commands.
kubectl apply -f bootstrap/eksctl/crossplane/aws-provider-vault-secret.yaml\nkubectl get ProviderRevision\n# example output\n# NAME HEALTHY REVISION IMAGE STATE DEP-FOUND DEP-INSTALLED AGE\n# provider-aws-a2e16ca2fc1a True 1 crossplane/provider-aws:v0.29.0 Active 23s\n
StoreConfig objects provides Crossplane and its providers information about how to connect to secret stores. These objects must be configured for external secret integrations to work.
Update the store config YAML file with your endpoint information. If you configured vault outside of the cluster, it should be the private IP address. e.g. 10.0.0.1:8200
This creates two configurations for secrets stores: - A configuration named in-cluster for Crossplane (compositions). This tells Crossplane to store composition secrets in the same cluster as Kubernetes secrets. - Another configuration named vault for AWS provider. This tells the provider to store secrets the vault instance under the /secret/crossplane-system namespace. To access the vault instance, a token is created by the sidecar at /vault/secrets/token.
The composition that is of interest is compositions/aws-provider/s3/multi-tenant.yaml. This composition demonstrates the following: - ProviderConfig selection based on the claim's namespace. - Publishes bucket information to Kubernetes secrets and Vault. - Published Vault secrets are created under the claim's namespace in Vault.
kubectl describe bucket\n# example events\n# Events:\n# Type Reason Age From Message\n# ---- ------ ---- ---- -------\n# Warning CannotConnectToProvider 1s (x5 over 14s) managed/bucket.s3.aws.crossplane.io cannot get referenced Provider: ProviderConfig.aws.crossplane.io \"default-provider-config\" not found\n
In the claim file, we specify a provider config name. However, this is patched out to use the provider config with name <NAMESPACE>-provider-config. This is why the error message indicates provider config with name default-provider-config is not found.
Since we created a provider config named application1-provider-config, we should be able to create a claim in namespace called application1.
#create namespace\nkubectl create ns application1 || true\n# create in new namespace\nkubectl apply -n application1 -f examples/aws-provider/composite-resources/s3/multi-tenant.yaml\n\nkubectl -n application1 get objectstorage\n# NAME READY CONNECTION-SECRET AGE\n# standard-object-storage True 22s\n
Once the claim reaches the ready state, you should be able to verify. Secret creation:
kubectl -n crossplane-system get secret `kubectl get xobjectstorage -o json | jq -r '.items[0].metadata.uid'` -o go-template='{{range $k,$v := .data}}{{printf \"%s: \" $k}}{{if not $v}}{{$v}}{{else}}{{$v | base64decode}}{{end}}{{\"\\n\"}}{{end}}'\n# example output\n# bucket-name: standard-object-storage-qlgvz-hz2dn\n# region: us-west-2\n
The same information should be available in Vault:
# in your vault installation\nvault kv get secret/crossplane-system/application1/dev/bucket\n# ==================== Secret Path ====================\n# secret/data/crossplane-system/application1/dev/bucket\n#\n# ======= Metadata =======\n# Key Value\n# --- -----\n# created_time 2022-07-22T20:51:27.852598176Z\n# custom_metadata map[awsblueprints.io/composition-name:s3bucket-multi-tenant.awsblueprints.io awsblueprints.io/environment:dev awsblueprints.io/provider:aws secret.crossplane.io/owner-uid:0c601153-358d-45e1-8e0a-0f34991bed82]\n# deletion_time n/a\n# destroyed false\n# version 1\n#\n# ====== Data ======\n# Key Value\n# --- -----\n# endpoint standard-object-storage-4p2wr-lxb74\n# region us-west-2\n
This is because the pod is created in the default namespace and the Vault policy we configured earlier does not allow it to access secrets in another namespace.