Runtime parameters are values that differ across environments, change over time, or are secrets that should not be casually revealed.
Properties like replicas of a deployment per environment, cluster level endpoints etc. are usually known in advance and should be checked into source code.
Some parameters cannot be checked into source code. These include tags for images produced by a CI build whose value should be subsequently used in the same build for a deployment, environment-specific secrets, etc.
qbec provides the following facilities for runtime parameters.
qbec.io/env
that contains the name of the environment used for the command.
This allows you to parameterize values based on the name of the environment. For instance, you could use the name
as a key in a map of variables keyed by environment name.qbec.io/envProperties
set to the properties defined in qbec.yaml
for the
environment. This is a good place to store static environment properties like cluster name, external endpoints
related to the cluster etc.qbec.yaml
with default values. Values for external
variables can be supplied on the command line. Default values are used when declared variables have not be set.
This mechanism allows you to develop components locally without having to specify variables on the command line
for every invocation and still set them explicitly when you actually apply the objects to the cluster.qbec.yaml
and associate them with components to which
they should be passed. In this case, you set defaults for these variables in the jsonnet code itself.The jsonnet tutorial explains external and top-level variables in detail along with the pros and cons of each.
Let’s examine each of these in turn.
You can access this variable using the std.extVar
function of the jsonnet standard library and customize parameters.
This allows you to create a parameters object that can be imported by your components.
local env = std.extVar('qbec.io/env'); // get the value of the environment
// define baseline parameters
local baseParams = {
components: {
service1: {
replicas: 1,
},
},
};
// define parameters per environment
local paramsByEnv = {
_: baseParams, // the baseline environment used by qbec for some commands
dev: baseParams {
components+: {
service1+: {
replicas: 2,
},
},
},
prod: baseParams {
components+: {
service1+: {
replicas: 3,
},
},
},
};
// return value for correct environment
if std.objectHas(paramsByEnv, env) then paramsByEnv[env] else error 'environment ' + env ' not defined in ' + std.thisFile
When there are many components and values, the above code can be split up into multiple files with each file
returning the object for a specific environment and the top-level file then import
s the other files.
Since variables and values are checked into source code when using this method, this is the most explicit way to set parameters and should be used as much as possible.
You declare the external variable(s) that you expect in qbec.yaml
.
spec:
vars:
external:
- name: service1_image_tag
default: latest
- name: service1_secret
default: changeme # fake value
secret: true # qbec debug logs will not print this value in cleartext
then you can use them in your components or runtime parameter config object, like so:
local imageTag = std.extVar('service1_image_tag');
and specify real values on the qbec command-line:
export service1_secret=XXX
qbec apply dev --vm:ext-str service1_image_tag=1.0.3 --vm:ext-str service1_secret
Note that we are explicitly setting the image tag to 1.0.3 but not providing the value for the secret on the command line. This causes qbec to set the secret value from the environment variable with the same name. This is the preferred method for passing in secrets.
You declare the top-level variable(s) used in your code associating them with the components that need them.
spec:
vars:
topLevel:
- name: service1Tag
components: [ 'service1' ]
- name: service1Secret
components: [ 'service1' ]
secret: true # qbec debug logs will not print this value in cleartext
In this case you would code your service1.jsonnet
component as follows:
function (service1Tag='latest', service1Secret='changeme') (
// for example: return an array containing a secret and a deployment
[
{
apiVersion: 'v1',
kind: 'Secret',
metadata: {
name: 'my-secret',
},
data: {
token: std.base64(service1Secret),
},
},
// ... etc
]
)
and specify real values on the qbec command-line:
export service1Secret=XXX
qbec apply dev --vm:tla-str service1Tag=1.0.3 --vm:tla-str service1Secret
--vm:ext-code foo=true
or --vm:tla-code foo=true
syntax. In the preceding examples, this will pass in a
true value that is a boolean instead of the string “true”. Use this judiciously. Requiring code variables might limit
your ability to define such variables in your IDE which may only allow string variables.imageTag
for 2 different services that need different image tags.null
. This is because qbec cannot tell the difference between a
default that was not specified versus one that was explicitly set to null
.qbec provides a --vm:ext-str-list
(and --vm:tla-str-list
) option that allows you to set a bunch of variables using a
single file. For example, if you have to set 3 variables, say, commit
, ci_job
, image_tag
for
every apply command, you can just set these environment variables and create a file like so:
commit
ci_job=1234
image_tag
You can set the values for each line if you so prefer. In the above example, the ci_job
variable is given an explicit value while the others are loaded from the environment.
Then --vm:ex-str-list=list-file
would automatically pull in the remaining values from the environment.
This file can be checked into source control and the variables can be provided with default values in
qbec.yaml
as usual.
To reduce duplication, you can even have your params generation code read this file and set parameters from external variables. For example:
local varList = std.split(importstr './list-vars.txt', '\n');
// remove blank lines
local lines = std.filter(function(x) x != '', varList);
// extract names
local names = std.map(function (x) std.split(x,'=')[0], lines);
local foldFunc = function(obj, key) obj { [key]: std.extVar(key) };
local externals = std.foldl(foldFunc, names, {});
// now externals is an object that can be used by the rest of the code
// it looks like: { commit: 'commit-id', ci_job: '1234', image_tag: '1.4-abc' }
The --strict-vars
flag for qbec commands can help you ensure correctness of your qbec command line invocation.
When this option is set, qbec:
This is a good option to set for CI builds to ensure that the build breaks if someone introduces a new variable
but fails to set a value for it for the apply
command, or if the variable name has a typo.