- Prerequisites
- Overview
- Gateway Management
- Graphman Bundle
- Restman Bundle
- Using Kustomize
- InitContainers
- Configure the Gateway
- Update the Gateway
- Validate the Update
Please make sure you've completed the steps here and have completed Lab Exercise 2 before beginning this exercise.
The Layer7 Operator supports the same deployment options for the Layer7 API Gateway as its Helm chart does, including support for bootstrapping bundles and initContainers. These options offer the Layer7 Operator multiple ways of managing Gateways short of using configuration as code in the form of Graphman bundles pulled from git or artifact repository sources of truth, concepts that will be explored in later exercises.
This exercise should familiarize you with some of those options, including bootstrapping bundles and using initContainers, and other related Kubernetes concepts and tools.
In this section, we will create and inspect a Kubernetes secret used to manage Gateway admin credentials.
First, create the secret:
kubectl create secret generic gateway-secret --from-literal SSG_ADMIN_USERNAME=admin --from-literal SSG_ADMIN_PASSWORD=7layer --from-literal SSG_CLUSTER_PASSWORD=7layer
Then, inspect the secret:
kubectl get secret gateway-secret -oyaml
Try using jsonpath
to inspect the secret values:
Linux/MacOS
kubectl get secret gateway-secret -o jsonpath="{.data.SSG_ADMIN_USERNAME}" | base64 -d
kubectl get secret gateway-secret -o jsonpath="{.data.SSG_ADMIN_PASSWORD}" | base64 -d
kubectl get secret gateway-secret -o jsonpath="{.data.SSG_CLUSTER_PASSWORD}" | base64 -d
Windows
kubectl get secret gateway-secret -o jsonpath="{.data.SSG_ADMIN_USERNAME}" > output.txt && certutil -decode output.txt decoded.txt > nul && type decoded.txt && del decoded.txt output.txt
kubectl get secret gateway-secret -o jsonpath="{.data.SSG_ADMIN_PASSWORD}" > output.txt && certutil -decode output.txt decoded.txt > nul && type decoded.txt && del decoded.txt output.txt
kubectl get secret gateway-secret -o jsonpath="{.data.SSG_CLUSTER_PASSWORD}" > output.txt && certutil -decode output.txt decoded.txt > nul && type decoded.txt && del decoded.txt output.txt
If you need to edit a secret value in place, you must provide a base64 encoded value that does not contain newlines. For example, the base64 encoded value in gateway-secret for SSG_ADMIN_PASSWORD is N2xheWVy
(for 7layer
).
Linux/MacOS
echo -n 7layer | base64
Output (correct format without a newline):
N2xheWVy
Windows
It's easiest to use an online tool for base64 encoding (and decoding) strings, or a text editor that supports the same.
There is a basic Graphman bundle that contains a single cluster-wide property here, ./exercise3-resources/cluster-property.json. Following the same process as before, we will create a secret that contains this bundle.
First, create the secret:
Linux/MacOS
kubectl create secret generic graphman-cluster-property-bundle --from-file=./exercise3-resources/cluster-property.json
Windows
kubectl create secret generic graphman-cluster-property-bundle --from-file=exercise3-resources\cluster-property.json
Then, inspect the secret:
kubectl get secret graphman-cluster-property-bundle -oyaml
Here is an example of the previous command's output (note that the key is cluster-property.json):
apiVersion: v1
data:
cluster-property.json: ewogICJjbHVzdGVyUHJvcGVydGllcyI6IFsKICAgIHsKICAgICAgIm5hbWUiOiAiYmFja2VuZDEiLAogICAgICAiZGVzY3JpcHRpb24iOiAiYSBjd3AiLAogICAgICAiaGlkZGVuUHJvcGVydHkiOiBmYWxzZSwKICAgICAgInZhbHVlIjogImh0dHBzOi8vbW9jay5icmNtbGFicy5jb20iCiAgICB9CiAgXQp9Cg==
kind: Secret
metadata:
creationTimestamp: "2023-09-21T05:54:14Z"
name: graphman-cluster-property-bundle
namespace: default
resourceVersion: "1130221"
uid: 6f567545-9f75-4d38-9533-63c759cc62e8
type: Opaque
When creating secrets from file you can specify the key. This also works for kustomize which we will be using shortly.
Note: For example only (these commands will fail because the secret already exists)
Linux/MacOS
kubectl create secret generic graphman-cluster-property-bundle --from-file=myclusterproperty.json=./exercise3-resources/cluster-property.json
Windows
kubectl create secret generic graphman-cluster-property-bundle --from-file=myclusterproperty.json=exercise3-resources\cluster-property.json
Though the Layer7 Operator is designed to primarily work with Graphman, Restman bundles can also be bootstrapped to container Gateways managed by the Layer7 Operator using secrets or other mechanisms.
There is a basic Restman bundle that contains a single cluster-wide property here, ./exercise3-resources/cluster-property.bundle. Following the same process as before, we will create a secret that contains this bundle.
First, create the secret:
Linux/MacOS
kubectl create secret generic restman-cluster-property-bundle --from-file=./exercise3-resources/cluster-property.bundle
Windows
kubectl create secret generic restman-cluster-property-bundle --from-file=exercise3-resources\cluster-property.bundle
Then, inspect the secret:
kubectl get secret restman-cluster-property-bundle -oyaml
Here is an example of the previous command's output:
apiVersion: v1
data:
cluster-property.bundle: PGw3OkJ1bmRsZSB4bWxuczpsNz0iaHR0cDovL25zLmw3dGVjaC5jb20vMjAxMC8wNC9nYXRld2F5LW1hbmFnZW1lbnQiPgogICAgPGw3OlJlZmVyZW5jZXM+CiAgICAgICAgPGw3Okl0ZW0+CiAgICAgICAgICAgIDxsNzpOYW1lPmJhY2tlbmQyPC9sNzpOYW1lPgogICAgICAgICAgICA8bDc6SWQ+aGM1N2NjMjYyMWI5MmEyMzMxZjYzY2MwZjBjMTQwMmM8L2w3OklkPgogICAgICAgICAgICA8bDc6VHlwZT5DTFVTVEVSX1BST1BFUlRZPC9sNzpUeXBlPgogICAgICAgICAgICA8bDc6UmVzb3VyY2U+CiAgICAgICAgICAgICAgICA8bDc6Q2x1c3RlclByb3BlcnR5IGlkPSJmYjU3Y2MyNjIxYjkyYTUzMzFmNjNjYzBmMGMzNDAxZCI+CiAgICAgICAgICAgICAgICAgICAgPGw3Ok5hbWU+YmFja2VuZDI8L2w3Ok5hbWU+CiAgICAgICAgICAgICAgICAgICAgPGw3OlZhbHVlPmh0dHBzOi8vbW9jazEuYnJjbWxhYnMuY29tPC9sNzpWYWx1ZT4KICAgICAgICAgICAgICAgIDwvbDc6Q2x1c3RlclByb3BlcnR5PgogICAgICAgICAgICA8L2w3OlJlc291cmNlPgogICAgICAgIDwvbDc6SXRlbT4KICAgIDwvbDc6UmVmZXJlbmNlcz4KICAgIDxsNzpNYXBwaW5ncz4KICAgICAgICA8bDc6TWFwcGluZyBhY3Rpb249Ik5ld09yVXBkYXRlIiBzcmNJZD0iZmI1N2NjMjYyMWI5MmE1MzMxZjYzY2MwZjBjMzQwMWQiIHR5cGU9IkNMVVNURVJfUFJPUEVSVFkiPgogICAgICAgICAgICA8bDc6UHJvcGVydGllcz4KICAgICAgICAgICAgICAgIDxsNzpQcm9wZXJ0eSBrZXk9Ik1hcEJ5Ij4KICAgICAgICAgICAgICAgICAgICA8bDc6U3RyaW5nVmFsdWU+bmFtZTwvbDc6U3RyaW5nVmFsdWU+CiAgICAgICAgICAgICAgICA8L2w3OlByb3BlcnR5PgogICAgICAgICAgICAgICAgPGw3OlByb3BlcnR5IGtleT0iTWFwVG8iPgogICAgICAgICAgICAgICAgICAgIDxsNzpTdHJpbmdWYWx1ZT5iYWNrZW5kMjwvbDc6U3RyaW5nVmFsdWU+CiAgICAgICAgICAgICAgICA8L2w3OlByb3BlcnR5PgogICAgICAgICAgICA8L2w3OlByb3BlcnRpZXM+CiAgICAgICAgPC9sNzpNYXBwaW5nPgogICAgPC9sNzpNYXBwaW5ncz4KPC9sNzpCdW5kbGU+
kind: Secret
metadata:
creationTimestamp: "2023-09-21T06:53:39Z"
name: restman-cluster-property-bundle
namespace: default
resourceVersion: "1136618"
uid: 7c9ae499-b0af-42f5-a77e-5090b9c073c1
type: Opaque
Kustomize introduces a template-free way to customize application configuration that simplifies the use of off-the-shelf applications. It is now built into kubectl as apply -k
.
Creating secrets or configmaps by hand can be useful for one off commands, but Kustomize is significantly more powerful (we're scratching the surface) and useful for idempotence. In this step we will go through how to create the same secrets with Kustomize.
./exercise3-resources/kustomization.yaml is preconfigured to create 3 secrets using the built-in secret generator.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
generatorOptions:
disableNameSuffixHash: true
secretGenerator:
- name: gateway-secret
env: ./secret.env
- name: graphman-cluster-property-bundle
files:
- ./cluster-property.json
- name: restman-cluster-property-bundle
files:
- ./cluster-property.bundle
Create the secrets using Kustomize. Kustomize expects a folder with a file called kustomization.yaml:
kubectl apply -k exercise3-resources
Because we had previously created these secrets, the output of the previous command may have warnings like these (that you can ignore):
Warning: resource secrets/gateway-secret is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
secret/gateway-secret configured
Warning: resource secrets/graphman-cluster-property-bundle is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
secret/graphman-cluster-property-bundle configured
Warning: resource secrets/restman-cluster-property-bundle is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
secret/restman-cluster-property-bundle configured
If you run the same command again, you will not see those warnings. For example:
secret/gateway-secret unchanged
secret/graphman-cluster-property-bundle configured
secret/restman-cluster-property-bundle configured
InitContainers are special containers that run before application containers in a pod. InitContainers can contain utilities or setup scripts not present in the application image. They can also share file system volumes with the application container making them useful for copying custom configuration as we will see now.
There is a basic initContainer here, ./exercise3-resources/basic-initcontainer. If you're familiar with the Gateway Helm Chart, you'll recognize the file/folder structure. There are additional examples here if you want to learn more about using initContainers with Layer7 API Gateways.
The basic initContainer that we provide works with a shared volume (/opt/docker/custom) and a bootstrap script that moves files from this folder to the correct locations on the Gateway container during startup.
This is what the initContainer does:
run_custom_scripts() {
scripts=$(find "./scripts" -type f 2>/dev/null)
for script in $scripts; do
filename=$(basename "$script")
ext=${filename##*.}
if [[ ${ext} == sh ]]; then
/bin/bash $script
elif [[ ${ext} == "py" ]] && [[ "${filename}" == *"preboot_"* ]]; then
python $script
fi
if [ $? -ne 0 ]; then
echo "Failed executing the script: ${i}"
fi
done
unset i
}
copyFiles() {
cp -r config/* /opt/docker/custom/
}
#run_custom_scripts
copyFiles
This is what the bootstrap script on the Gateway does
#!/bin/bash
BASE_CONFIG_DIR="/opt/docker/custom"
GRAPHMAN_CONFIG_DIR="/opt/docker/graphman"
BUNDLE_DIR="$BASE_CONFIG_DIR/bundle"
CUSTOM_ASSERTIONS_DIR="$BASE_CONFIG_DIR/custom-assertions"
MODULAR_ASSERTIONS_DIR="$BASE_CONFIG_DIR/modular-assertions"
EXTERNAL_LIBRARIES_DIR="$BASE_CONFIG_DIR/external-libraries"
CUSTOM_PROPERTIES_DIR="$BASE_CONFIG_DIR/custom-properties"
CUSTOM_HEALTHCHECK_SCRIPTS_DIR="$BASE_CONFIG_DIR/health-checks"
CUSTOM_SHELL_SCRIPTS_DIR="$BASE_CONFIG_DIR/scripts"
BASE_TARGET_DIR="/opt/SecureSpan/Gateway"
GRAPHMAN_BOOTSTRAP_DIR="$BASE_TARGET_DIR/node/default/etc/bootstrap/bundle"
TARGET_CUSTOM_ASSERTIONS_DIR="$BASE_TARGET_DIR/runtime/modules/lib"
TARGET_MODULAR_ASSERTIONS_DIR="$BASE_TARGET_DIR/runtime/modules/assertions"
TARGET_EXTERNAL_LIBRARIES_DIR="$BASE_TARGET_DIR/runtime/lib/ext"
TARGET_BUNDLE_DIR="$BASE_TARGET_DIR/node/default/etc/bootstrap/bundle"
TARGET_CUSTOM_PROPERTIES_DIR="$BASE_TARGET_DIR/node/default/etc/conf"
TARGET_HEALTHCHECK_DIR="/opt/docker/rc.d/diagnostic/health_check"
error() {
echo "ERROR - ${1}" 1>&2
exit 1
}
function cleanup() {
echo "***************************************************************************"
echo "removing $BASE_CONFIG_DIR"
echo "***************************************************************************"
rm -rf $BASE_CONFIG_DIR/*
}
function copy() {
TYPE=$1
EXT=$2
SOURCE_DIR=$3
TARGET_DIR=$4
echo "***************************************************************************"
echo "scanning for $TYPE in $SOURCE_DIR"
echo "***************************************************************************"
FILES=$(find $3 -type f -name '*'$2 2>/dev/null)
for file in $FILES; do
name=$(basename "$file")
cp $file $4/$name
echo -e "$name written to $4/$name"
done
}
function gunzip() {
TYPE=$1
EXT=$2
SOURCE_DIR=$3
echo "***************************************************************************"
echo "scanning for $TYPE in $SOURCE_DIR"
echo "***************************************************************************"
FILES=$(find $3 -type f -name '*'$2 2>/dev/null)
for file in $FILES; do
fullname=$(basename "$file")
name="${fullname%.*}"
cat $file | gzip -d > $GRAPHMAN_BOOTSTRAP_DIR/$name".json"
echo -e "$name decompressed"
done
}
function run() {
TYPE=$1
EXT=$2
SOURCE_DIR=$3
echo "***************************************************************************"
echo "scanning for $TYPE in $SOURCE_DIR"
echo "***************************************************************************"
FILES=$(find $3 -type f -name '*'$2 2>/dev/null)
for file in $FILES; do
name=$(basename "$file")
echo -e "running $name"
/bin/bash $file
if [ $? -ne 0 ]; then
echo "Failed executing the script: $file"
exit 1
fi
done
}
gunzip "graphman bundles" ".gz" $GRAPHMAN_CONFIG_DIR
copy "bundles" ".bundle" $BUNDLE_DIR $TARGET_BUNDLE_DIR
copy "custom assertions" ".jar" $CUSTOM_ASSERTIONS_DIR $TARGET_CUSTOM_ASSERTIONS_DIR
copy "modular assertions" ".aar" $MODULAR_ASSERTIONS_DIR $TARGET_MODULAR_ASSERTIONS_DIR
copy "external libraries" ".jar" $EXTERNAL_LIBRARIES_DIR $TARGET_EXTERNAL_LIBRARIES_DIR
copy "custom properties" ".properties" $CUSTOM_PROPERTIES_DIR $TARGET_CUSTOM_PROPERTIES_DIR
copy "custom health checks" ".sh" $CUSTOM_HEALTHCHECK_SCRIPTS_DIR $TARGET_HEALTHCHECK_DIR
run "custom shell scripts" ".sh" $CUSTOM_SHELL_SCRIPTS_DIR
We can now update our Gateway Custom Resource with all of the additional parts that we've configured.
For this we will be configuring this file, ./exercise3-resources/gateway.yaml
.
Additional documentation for Gateway custom resources can be found here.
First, reference the Gateway secret that we created by uncommenting secretName
(~ line 32) and deleting the following username
and password
lines. For example:
...
management:
secretName: gateway-secret
service:
...
Next, enable the bootstrap script (~ line 27) and add an initContainer. For example:
...
bootstrap:
script:
enabled: true
initContainers:
- name: workshop-init
image: docker.io/layer7api/workshop-init:1.0.0
imagePullPolicy: IfNotPresent
volumeMounts:
- name: config-directory
mountPath: /opt/docker/custom
...
Finally, references the bundles that we created by replacing the bundle array (~ line 26) as follows:
...
bundle:
- type: restman
source: secret
name: restman-cluster-property-bundle
- type: graphman
source: secret
name: graphman-cluster-property-bundle
...
We can now apply the Gateway custom resource updates. The Layer7 Operator will then reconcile actual state with our new desired state.
First, if you're not still tailing the Layer7 Operator logs from the previous lab exercise, then start doing that now in a separate terminal (you may have to set your KUBECONFIG environment variable in the new terminal):
kubectl logs -f -l control-plane=controller-manager -c manager
Next, apply the updated manifest:
Linux/MacOS
kubectl apply -f ./exercise3-resources/gateway.yaml
Windows
kubectl apply -f exercise3-resources\gateway.yaml
Now test the update by calling an API and connecting with Policy Manager.
First, find the external IP address for the Gateway service in your namespace:
kubectl get svc ssg
Here is an example of the previous command's output. In this example, the external IP address is 34.168.26.20. Yours will be different.
NAME TYPE CLUSTER-IP ***EXTERNAL-IP*** PORT(S) AGE
ssg LoadBalancer 10.96.14.218 34.168.26.20 8443:32060/TCP,9443:30632/TCP 80s
Next, try calling an API on the Gateway using your external IP address. For example:
curl -k https://<your-external-ip>:8443/helloworld
The API should respond as follows:
Hello World!
Finally, connect to your Gateway with Policy Manager to view the bootstrapped bundles:
User Name: admin
Password: 7layer
Gateway: <your-external-ip>