Overview
kctrl is a CLI for working with the kapp-controller. One of the tasks that kctrl can perform is to make the process of creating carvel packages easier.
The following steps continue on from the preparation page and develop an example package and repository.
Directory Structure
To manage multiple packages, we need to create a directory structure to organise the files.
- Setup the directory structure.
# Create the parent folders for packages and repositories.
mkdir --parents "${CARVEL_PACKAGE_HOME}/"{packages,repos}
# Create the package folder
mkdir --parents "${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}"
# Create the repo folder
mkdir --parents "${CARVEL_PACKAGE_HOME}/repos/${CARVEL_PACKAGE_REPO}"
# OPTIONAL: These last folders are optional and opinionated steps to layout the structure in a particular way.
# Optional: Create a directory where all package configuration and ytt will live.
mkdir --parents "${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}"/config/{kapp,kbld,ytt}
# Optional: Create the structure for ytt
mkdir --parents "${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}"/config/ytt/{templates,overlays}
# Optional: Create a location where Package Consumers can look for examples
mkdir --parents "${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}"/examples
# Optional: Create the folder where tests for the package are located
mkdir --parents "${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}"/tests
Namespace
- In your development cluster, create a namespace and SA where we will manage Carvel packages from and set the kubectl context.
# Create the namespace
kubectl create ns ${CARVEL_PACKAGE_NAMESPACE}
# Create a SA inside the namespace
kubectl create sa -n ${CARVEL_PACKAGE_NAMESPACE} ${CARVEL_PACKAGE_SA}
# As this is just for local development, YOLO create a loose ClusterRole
kubectl create clusterrole \
"${CARVEL_PACKAGE_NAMESPACE}-kapp-controller" \
--resource='*.*' \
--non-resource-url='*' \
--verb='*'
# Create a ClusterRoleBinding for the new SA
kubectl create clusterrolebinding \
"${CARVEL_PACKAGE_NAMESPACE}-kapp-controller" \
--serviceaccount "${CARVEL_PACKAGE_NAMESPACE}:${CARVEL_PACKAGE_SA}" \
--clusterrole "${CARVEL_PACKAGE_NAMESPACE}-kapp-controller"
# Set your working context into the correct namespace.
kubectl config set-context --current --namespace ${CARVEL_PACKAGE_NAMESPACE}
Package
Init
- Create the templates used by the initialization process.
# PackageBuild
cat <<- EOF > "${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}/package-build.yml"
---
apiVersion: kctrl.carvel.dev/v1alpha1
kind: PackageBuild
metadata:
creationTimestamp: "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
name: ${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}
spec:
release:
- resource: {}
template:
spec:
app:
spec:
template:
- ytt:
paths:
- config/kapp/Config.yaml
- upstream
- config/ytt
- kbld:
paths:
- config/kbld/Config.yaml
deploy:
- kapp:
waitTimeout: 60m
rawOptions:
- --diff-changes=true
- --wait=true
- --apply-concurrency=3
export:
- imgpkgBundle:
image: ${OCI_REGISTRY}/${CARVEL_PACKAGE_NAMESPACE}/packages/${CARVEL_PACKAGE_NAME}
useKbldImagesLock: true
includePaths:
- upstream
- config
EOF
# PackageMetadata
cat <<- EOF > "${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}/package-resources.yml"
---
apiVersion: data.packaging.carvel.dev/v1alpha1
kind: PackageMetadata
metadata:
creationTimestamp: "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
name: ${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}
spec:
displayName: ${CARVEL_PACKAGE_NAME}
longDescription: ${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}
shortDescription: ${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}
maintainers: []
categories: []
EOF
# Package Readme
cat <<- EOF > "${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}/README.md"
# Carvel package for ${CARVEL_PACKAGE_NAME}
This Carvel package provides << awesome functionality >>
## Requirements
This package has the following requirements.
- Kubernetes \`v0.0.0\`
- Kapp Controller \`v0.0.0\`
## Components
## Configuration
The following configuration values can be set to customize the ${CARVEL_PACKAGE_NAME} installation.
### Global
| Value | Required/Optional | Description |
|-------|-------------------|-------------|
| \`name\` | Optional | description |
## Usage Examples
There are starting templates available in [examples](./examples)
EOF
- Now it’s time to use the
kctrltool to initialize the package using the interactive wizard.
kctrl --debug \
package init \
--chdir "${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}"
# Once you have completed the wizard, take a look at the folder structure.
tree -a "${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}"
- Create a build values file which will be read in when releasing the package.
cat <<- EOF >> "${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}/build-values.yml"
# Name: build-values.yml
# Description: Carvel package build values for ${CARVEL_PACKAGE_NAME}
EOF
Now that the package interactive initialization process is complete, edit the package files as required. For example you might like to;
- Add additional directories to the export
includePathsinpackage-build.yml - Add additional directories to the
yttpaths inpackage-build.yml - Add custom
kappdeployment flags inpackage-build.yml - Add additional
yttcustomizations and overlays - Modify the
package-resources.ymlto include config from secrets.
- Add additional directories to the export
# Example to modify the PackageInstall to look for a secret for the values.
yq eval-all \
--inplace \
--expression \
'
.
| select(.kind == "PackageInstall")
|= (
.spec.values[0].secretRef.name = ("${CARVEL_PACKAGE_NAME}-values" | envsubst(nu)),
.spec.serviceAccountName = ("${CARVEL_PACKAGE_SA}" | envsubst(nu))
)
' \
"${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}/package-resources.yml"
Develop
This is where we use kctrl to assist with the inner development loop of testing the package locally before release.
kctrl --debug \
dev \
--delete \
--local \
--kbld-build \
--namespace "${CARVEL_PACKAGE_NAMESPACE}" \
--file "${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}/package-resources.yml"
Release
Release the Package
- Make sure you have logged into the registry
docker login ${OCI_REGISTRY}
- Create the initial template used for the
PackageRepository
cat <<- EOF > "${CARVEL_PACKAGE_HOME}/repos/${CARVEL_PACKAGE_REPO}/pkgrepo-build.yml"
---
apiVersion: kctrl.carvel.dev/v1alpha1
kind: PackageRepositoryBuild
metadata:
creationTimestamp: "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
name: ${CARVEL_PACKAGE_REPO}.${CARVEL_PACKAGE_FQDN}
spec:
export:
imgpkgBundle:
image: ${OCI_REGISTRY}/carvel/repos/${CARVEL_PACKAGE_REPO}
EOF
- Once you are ready to release the package, you can generate the
PackageandPackageMetadataCRDs usingrelease.
kctrl --debug \
package release \
--openapi-schema=true \
--build-ytt-validations=true \
--chdir "${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}" \
--build-values "build-values.yml" \
--copy-to "artifacts" \
--repo-output "${CARVEL_PACKAGE_HOME}/repos/${CARVEL_PACKAGE_REPO}" \
--tag "build-$(date +%Y-%m-%d).$((CARVEL_PACKAGE_COUNTER++))" \
--version "${CARVEL_PACKAGE_VERSION}"
Release the Repository
- Publish the Package Repository using the previously build
PackageandPackageMetadata.
kctrl --debug \
package repository release \
--chdir "${CARVEL_PACKAGE_HOME}/repos/${CARVEL_PACKAGE_REPO}" \
--copy-to "${CARVEL_PACKAGE_HOME}/repos/${CARVEL_PACKAGE_REPO}/pkgrepo-build.yml" \
--version "$(date +%Y-%m-%d)"
Examples
Now that the package is ready, create ready-made examples used by Package Consumers. These are files are used to add to GitOps configurations or applied directly with kubectl.
- Create the example
PackageRepositorythat contains the package just built.
cp --force \
"${CARVEL_PACKAGE_HOME}/repos/${CARVEL_PACKAGE_REPO}/package-repository.yml" \
"${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}/examples/PackageRepository.yaml"
- Create the example
PackageInstallthat refers to the package just built.
# Split the file into multiple but only get the PackageInstall
yq eval-all \
'select(.kind == "PackageInstall")' "carvel/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}/package-resources.yml" \
| \
yq \
--split-exp \
'
("${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}/examples/" + .kind + ".yaml" | envsubst(nu))
'
# Bump the version number in the example to match the latest version.
yq \
--inplace \
--expression \
'
.spec.packageRef.versionSelection
|= .constraints = ("${CARVEL_PACKAGE_VERSION}" | envsubst(nu))
' \
"carvel/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}/examples/PackageInstall.yaml"
- Create an example
Secretthat contains default values file for thePackageInstall
cat <<- EOF > "${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}/examples/Secret.yaml"
---
apiVersion: v1
kind: Secret
metadata:
name: ${CARVEL_PACKAGE_NAME}-values
namespace: ${CARVEL_PACKAGE_NAMESPACE}
annotations:
tkg.tanzu.vmware.com/tanzu-package: ${CARVEL_PACKAGE_NAME}
stringData:
values.yaml: |
---
# Place value overrides for your specific package here.
EOF
Test
So far we have;
- Developed the Package
- Validated the Package Locally
- Released the Package to the Registry
- Updated the Package in the Package Repository
- Created examples for Package Consumers
The final test is to put our Package Consumer hat on and try out the the package from the Package Repository. In order to do this we will;
- Apply a
PackageRepositoryCRD to the Cluster to know where to obtain packages from. - Apply a
Secretthat our Carvel package will read installation values from - Apply the
PackageInstallobject to install the Carvel package we made previously.
Let’s begin.
- Apply the latest
PackageRepositoryto the Cluster which includes the recently build package.
# Deploy the Package Repository
kubectl apply -f "${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}/examples/PackageRepository.yaml"
# NOTE: This file is pinned to a specific shasum.
# During development is can be easier to point to the latest mutable tag.
# Then you can kick the pkgr to force a reconcile to the latest changes.
# Wait until the Package Repository has reconciled before continuing
kubectl get pkgr -n ${CARVEL_PACKAGE_NAMESPACE}
# Verify you can now see the package as available.
kubectl get pkgm -n ${CARVEL_PACKAGE_NAMESPACE}
# Show all available versions of the package.
kubectl get pkg -n ${CARVEL_PACKAGE_NAMESPACE}
- Apply a
Secretwhere the Carvel package values will be managed from.
kubectl apply \
--filename \
"${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}/examples/Secret.yaml"
- Install the Carvel package imperatively for testing and watch the
kapp-controllergo to work.
# Imperative option for testing.
kctrl package install \
--service-account-name "${CARVEL_PACKAGE_SA}" \
--package-install ${CARVEL_PACKAGE_NAME} \
--package ${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN} \
--namespace "${CARVEL_PACKAGE_NAMESPACE}" \
--version ${CARVEL_PACKAGE_VERSION} \
--wait=true \
--dry-run
# Or, use the example like kapp-controller would do.
kubectl apply \
--filename "${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}/examples/PackageInstall.yaml"
# Optionally, you can pass --values-file to provide testing overrides.
- TIP: When testing your package it can be useful to
kickthe resources forcing a reconcile loop after making changes.
# While bugs are true...
# Make a change...
# Apply all the things
kubectl apply \
--filename "${CARVEL_PACKAGE_HOME}/packages/${CARVEL_PACKAGE_NAME}.${CARVEL_PACKAGE_FQDN}/examples"
# Kick the Package repository to force a reconcile.
kctrl package repository kick \
--repository "${CARVEL_PACKAGE_REPO}.${CARVEL_PACKAGE_FQDN}" \
--namespace "${CARVEL_PACKAGE_NAMESPACE}" \
--yes
# Kick the Package install to force a reconcile
kctrl package installed kick \
--package-install ${CARVEL_PACKAGE_NAME} \
--namespace "${CARVEL_PACKAGE_NAMESPACE}" \
--yes
# Done...
