Package Configuration
In the context of the Package Operator, package configuration enables you to customize your resource deployments using templates. This documentation will introduce you to the core concepts of package configuration, how it works, and how you can use it to streamline your application deployments.
Understanding Package Configuration
You can think of package configuration as a set of customizable knobs and switches that you can turn to adjust how your application or operator gets deployed. Using these knobs, you can adjust the deployment to your needs without changing the actual code. Package configuration includes defining how you create and customize resources using templates.
The main components involved in package configuration are:
- Templates: These files have placeholders that you will replace with values from the configuration context.
- Configuration Definition: The configuration definition specifies the structure of the configuration parameters that you can customize when deploying a package. It provides information about properties, data types, default values, and required or optional settings.
- Configuration Context: During deployment, the configuration context is the set of values and information used to replace placeholders in templates. It includes details about the package, images, configuration settings, and the environment. The configuration context contains the values that apply to a particular deployment, so you can tailor the package to fit your needs.
Benefits of Package Configuration
Package configuration offers several advantages when deploying applications or operators using the Package Operator:
Simplified deployment: Package configuration streamlines the deployment process by allowing you to define customizable values without modifying the underlying resource templates.
Consistency: With package configuration, you ensure consistency across deployments, as you can enforce predefined configurations for different environments.
Customization without code changes: Package configuration enables you to tailor your deployment without altering the application or operator codebase. You can easily switch configurations for various use cases.
Defining Package Configuration
You have the option to include a config
section within your packages.
Define this section within the package manifest using OpenAPI
specifications. It’s good practice to define values that are
essential for successful package deployment as required
, and to set
default values where applicable. By incorporating default values, you
prevent deployment failures caused by missing mandatory values.
NOTE: These two solutions are mutually
exclusive. You can either mark a property as
required
to enforce the necessity of providing
a value, thereby preventing failure, or assign a
default value to ensure success even
when no value is provided. It’s important not to
use both approaches for the same property.
When creating your package, consider the essential configuration parameters that you will need to customize. Provide default values for properties that you can set up, but you can change them if you want to.
Here’s a simple example of specifying a configuration schema in your package manifest.yaml
:
spec:
config:
openAPIV3Schema:
properties:
nginxImage:
description: nginxImage is the reference to the image
containing nginx.
type: string
default: "nginx:latest"
nginxWebrootConfigmap:
description: nginxWebrootConfigmap is the name of an
already existing configmap in the package namespace that
will be mounted into the nginx containers as webroot folder.
type: string
required:
- nginxWebrootConfigmap
type: object
This example sets the default nginxImage
to nginx:latest
and declares the key
nginxWebrootConfigmap
as required.
Next, you add the following to the template section of your deployment.yaml.gotmpl
:
template:
metadata:
labels:
app.kubernetes.io/name: nginx
app.kubernetes.io/instance: "{{ .package.metadata.name }}"
spec:
containers:
- name: nginx
image: "{{ .config.nginxImage }}"
ports:
- containerPort: 80
volumeMounts:
- name: nginx-webroot
mountPath: /usr/share/nginx/html
volumes:
- name: nginx-webroot
configMap:
name: nginx-webroot
Finally, you configure the example package.yaml
as follows and deploy it:
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-webroot
data:
index.html: |-
<html>
<body>
<h1>hi there</h1>
</body>
</html>
---
apiVersion: package-operator.run/v1alpha1
kind: Package
metadata:
name: nginx-webroot
spec:
image: quay.io/yourquayusername/nginx:tag
config:
nginxWebrootConfigmap: nginx-webroot
Verify the configuration by running curl
on the deployment.
Example:
kubectl exec deploy/nginx-webroot -- curl -s localhost
Example Output:
<html>
<body>
<h1>hi there</h1>
</body>
Examples
Let’s examine a couple more examples to illustrate how package configuration works.
Example 1: Customizing Image Versions
Suppose you have multiple applications with different frontend and backend images and ports. You want users to easily customize the images and ports based on the application. Here’s a step-by-step process:
Create a PackageManifest
Define the scope and phases of your package in yourmanifest.yaml
:apiVersion: manifests.package-operator.run/v1alpha1 kind: PackageManifest metadata: name: frontend-backend spec: scopes: - Namespaced phases: - name: deploy availabilityProbes: - probes: - condition: type: Available status: "True" - fieldsEqual: fieldA: .status.updatedReplicas fieldB: .status.replicas selector: kind: group: apps kind: Deployment
Define Package Configuration
Under theconfig
section, add the configuration schema for your package to yourmanifest.yaml
. Allow users define image and port versions.config: openAPIV3Schema: properties: frontendImage: description: frontendImage is the reference to the image containing the frontend application. type: string databaseImage: description: backendImage is the reference to the image containing the backend application. type: string frontendPort: description: Port for the frontend container. type: integer default: 80 databasePort: description: Port for the database container. type: integer default: 5432 type: object
Add the following to the template section of your
deployment.yaml.gotmpl
:template: metadata: labels: app.kubernetes.io/name: frontend-backend app.kubernetes.io/instance: "{{ .package.metadata.name }}" spec: containers: - name: frontend image: "{{ .config.frontendImage }}" ports: - containerPort: {{ .config.frontendPort }} - name: database image: "{{ .config.databaseImage }}" ports: - containerPort: {{ .config.databasePort }}
Build and push your package to your image repository:
kubectl package validate <your-template-directory>
kubectl package build -t <your-image-reference-goes-here> --push <your-template-directory>
Apply Custom Configuration
In your Package resource, users can add values for the configured properties. For example:
apiVersion: package-operator.run/v1alpha1 kind: Package metadata: name: frontend-backend-deploy spec: image: quay.io/yourquayusername/frontend-backend-deploy:v2 config: // configuration for abc-app frontendImage: quay.io/yourquayusername/nginx:abc-appv1 databaseImage: quay.io/yourquayusername/postgres:abc-appv14.5 frontendPort: 8080 databasePort: 5433
Deploy the package:
kubectl create -f package.yaml
Verify the package status:
kubectl get packages
Example 2: Using Package Configuration in a Deployment Template with a ClusterObjectTemplate
Imagine you are an engineer responsible for managing deployments in a Kubernetes cluster using the Package Operator. You have a package application, and you want to streamline the stage and production deployment process by using package configuration and ClusterObjectTemplates.
Application Configuration
- You have built your package application as a container image and
pushed it to a registry such as
quay.io/yourusername/frontend-deployment:v1
. - Your deployment template (
deployment.yaml.gotmpl
) consists of Package Configuration for app name, image, and namespace.
Example deployment.yaml.gotmpl
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .package.metadata.name }}-deployment
namespace: "{{ .config.namespace }}"
labels:
app.kubernetes.io/name: "{{ .config.app }}"
app.kubernetes.io/instance: "{{ .package.metadata.name }}"
annotations:
package-operator.run/phase: deploy
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: "{{ .config.app }}"
template:
metadata:
labels:
app.kubernetes.io/name: "{{ .config.app }}"
spec:
containers:
- name: "{{ .config.app }}"
image: "{{ .config.image }}"
Example manifest.yaml
:
apiVersion: manifests.package-operator.run/v1alpha1
kind: PackageManifest
metadata:
name: my-deployment-package
spec:
scopes:
- Namespaced
- Cluster
phases:
- name: deploy
availabilityProbes:
- probes:
- condition:
type: Available
status: "True"
- fieldsEqual:
fieldA: .status.updatedReplicas
fieldB: .status.replicas
selector:
kind:
group: apps
kind: Deployment
config:
openAPIV3Schema:
properties:
namespace:
description: The namespace to deploy to.
type: string
app:
description: The name of the application.
type: string
image:
description: The image to use for the deployment.
type: string
required:
- namespace
- app
- image
type: object
Create a ConfigMap with Application Configuration:
Create a ConfigMap that stores the configuration data for your application.
apiVersion: v1 kind: ConfigMap metadata: name: myapp-config namespace: integration data: namespace: production-namespace image: quay.io/yourusername/wizard:stable app: wizard
Apply the ConfigMap to your cluster:
kubectl apply -f configmap.yaml
Create a ClusterObjectTemplate:
Create a ClusterObjectTemplate that references the ConfigMap and uses it to template the PackageManifest.
Use the following YAML and apply it:
apiVersion: package-operator.run/v1alpha1 kind: ClusterObjectTemplate metadata: name: cool-new-app-template namespace: integration spec: sources: - apiVersion: v1 items: - destination: .namespace key: .data.namespace - destination: .image key: .data.image - destination: .app key: .data.app kind: ConfigMap name: myapp-config namespace: integration optional: false template: | apiVersion: package-operator.run/v1alpha1 kind: Package metadata: name: "{{ .config.name }}" namespace: "{{ .config.namespace }}" spec: image: "quay.io/yourusername/frontend-deployment:v1" config: {{toJson .config}}
Apply the ClusterObjectTemplate:
Apply the ClusterObjectTemplate using the
kubectl apply -f clusterobjecttemplate.yaml
command.Use the
kubectl describe clusterobjecttemplates <your-template-name>
command to verify the ClusterObjectTemplate status.Example:
kubectl describe clusterobjecttemplates cool-new-app-template ... snipped for readability Status: Conditions: Last Transition Time: 2023-09-09T20:17:47Z Message: Unpack job succeeded Observed Generation: 1 Reason: UnpackSuccess Status: True Type: Unpacked
Create Packages Based on ClusterObjectTemplate:
When the ClusterObjectTemplate is applied, it creates Package objects based on the initial configuration from the ConfigMap.
These Package objects represent your deployments. You can view the created Package objects using
kubectl get packages -A
andkubectl get deployments -A
to view the corresponding deployments.Example:
kubectl get packages -A NAMESPACE NAME STATUS AGE production-namespace wizard Available 3m21s
kubectl get deployments -n production-namespace NAME READY UP-TO-DATE AVAILABLE AGE wizard-deployment 3/3 3 3 5m15s
Update the Application Configuration:
Update the configuration of your application and modify the data in the
myapp-config
ConfigMap.Example:
apiVersion: v1 kind: ConfigMap metadata: name: myapp-config namespace: integration data: namespace: production-namespace image: quay.io/yourusername/wizard:v3 app: wizard
Apply the ConfigMap:
Apply the ConfigMap by issuing:
kubectl apply -f configmap.yaml
.The package status will change status to Progressing and then Available:
Example:
kubectl get packages -A NAMESPACE NAME STATUS AGE production-namespace wizard Progressing 7m43s
kubectl get packages -A NAMESPACE NAME STATUS AGE production-namespace wizard Available 8m11s
Verify the deployment:
Verify the deployment is using the updated image:
kubectl describe deployment wizard-deployment -n production-namespace ... Pod Template: Labels: app.kubernetes.io/name=wizard Containers: wizard: Image: wizard:v3 Port: <none> Host Port: <none> Environment: <none> Mounts: <none> Volumes: <none>
Update the ConfigMap for Stage Deployment:
To create a deployment for stage, update the ConfigMap with the details of the stage deployment.
Example:
apiVersion: v1 kind: ConfigMap metadata: name: myapp-config namespace: integration data: namespace: staging-namespace image: quay.io/yourusername/wizard:2.0.0-beta app: wizard
Apply the ConfigMap to your cluster:
Apply the ConfigMap to your cluster, by issuing:
kubectl apply -f configmap.yaml
When you apply the ConfigMap, the wizard package is created in the staging-namespace, along with the deployments.
Verify the Package object creation and deployments:
kubectl get packages -A NAMESPACE NAME STATUS AGE production-namespace wizard Available 70m staging-namespace wizard Available 51s
kubectl get deployments -A production-namespace wizard-deployment 3/3 3 3 73m staging-namespace wizard-deployment 3/3 3 3 4m47s
These steps guided you through deploying and updating your application using package configuration and ClusterObjectTemplates with the Package Operator.
Error Messages and Troubleshooting
During the package configuration process, you may encounter common errors such as missing or incorrectly formatted values. Here are some troubleshooting tips:
- Check Property Names: Ensure that property names in your configuration match those defined in the package manifest’s configuration schema.
- Data Type Mismatch: Verify that the data types of configured values match the expected types in the schema.
- Test Framework: Use the Package Operator test framework to test your templates.
- Review Logs: Review the Package Operator manager logs using
the
kubectl logs -f package-operator-manager<pod> -n package-operator-system
command.
Conclusion
These examples illustrate how you can leverage package configuration to provide a streamlined and customized way to deploy applications in Kubernetes using the Package Operator. The power of package configuration lies in its ability to abstract and parameterize values in your resources, making deployments flexible and adaptable.