Many microservices applications are primarily configured through environment
variables nowadays. If you’re deploying to Cloud Run with gcloud
CLI
specifying a lot of environment variables might look rather painful:
--set-env-vars="LOG_LEVEL=verbose,EXPERIMENTS=ShowInactiveUsers,CountIncorrectLoginAttempts,"Test 1",DB_CONN=sqlserver://username@host/instance?param1=value¶m2=value,DB_PASS=berglas://my-bucket/path/to/my-secret?destination=tempfile,GODEBUG=schedtrace=9000"
Don’t be scared! There’s a better way to do this. I’ve explained all this in the Cloud Run documentation but this article will have some discussion.
An astute reader might immediately notice that the example above won’t work:
gcloud
expects to splitK=V
pairs using commas, though some values have commas in them- There are unescaped
"
characters inside an argument delimited by"
, will cause bash to fail parsing - If you run this in bash, it will strip off parts like
"..."
before passing it to thegcloud
command.
So in this article I’ll give you several tips to avoid thinking about this:
- Do not cram all environment variables into a single option.
- Prevent your shell (e.g. bash) from interpreting arguments as much as you can.
- If things are getting out of control, switch to declarative resource model with YAML.
1. Too many environment variables? Split them
When you run the
gcloud run deploy
command the second time, you have two way of specifying
environment variables:
- specify all environment variables you need again, with
--set-env-vars
. - specify only the environment variables to update
--update-env-vars
and unset the ones you want with--remove-env-vars
.
I personally prefer the first option, as I write a deploy.sh
for my apps with
the fully deploy command. So how do you cope with too many environment
variables?
Cloud Run actually allows you to specify these --set-env-vars
or
--update-env-vars
options multiple times, so you can split the declaration
above into multiple arguments simply as:
$ gcloud run deploy \
--set-env-vars LOG_LEVEL=verbose \
--set-env-vars "EXPERIMENTS=CountIncorrectLoginAttempts,\"Test 1\"" \
--set-env-vars GODEBUG=schedtrace=9000
[...]
That’s better, but is it fool proof? No. As you might see, we started to do ugly things like escaping quotes, and there are more hidden corner cases.
2. Prevent misinterpretation of env vars
If you pass this to gcloud
, it will treat the comma (,
) character as another
environment variable declaration and will try to split the value into multiple
environment variables:
[...]
--set-env-var "A=B,C,D"
This is by design, and is explained here.
To prevent splitting with commas, you need to specify a different custom
delimiter that you’re sure won’t occur in your value string, such as ##
:
[...]
--set-env-vars "^##^A=B,C,D"
Yeah, it’s not the best solution, but it will do the trick. Here are two other examples that might give you trouble while running in the shell:
--set-env-vars A="B C D"
: this will not preserve the quotes, but ensures the spaces in the value are not split by your shell.--set-env-vars 'A=B C D'
: single-quotes are a usually fool-proof way of preserving what’s listed in them (but what if your value actually has a single quote? —escaping those correctly can be a pain!)--set-env-vars 'A="B C D"'
: this will preserve the double-quotes in the value.
3. Read environment variables from a file
gcloud run deploy
does not support the --env-vars-file
unlike Google Cloud
Functions or Google App Engine deploy commands.
However, you can actually deploy your entire Cloud Run service from a manifest file written in YAML. This works because Cloud Run services are actually Knative API objects as I explained in an earlier article.
This is done with the gcloud beta run services replace
command (hoping this’ll
graduate to beta → GA soon!) In this yaml file, take a look at the env
section
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: hello
spec:
template:
spec:
containers:
- image: gcr.io/google-samples/hello-app:1.0
env:
- name: LOG_LEVEL
value: verbose
# we can break strings over multiple lines
# see: https://yaml-multiline.info/
- name: EXPERIMENTS
value: 'ShowInactiveUsers,
CountIncorrectLoginAttempts,
"Test 1"'
- name: DB_CONN
value: sqlserver://username@host/instance?param1=value¶m2=value
# [...]
Then you can run this every time you want to update your Service:
gcloud beta run services replace FILE.yaml
Are there caveats with this method? Yes, now we’re bound by the rules of the
YAML syntax. If you have something like value: 5
or value: true
, these won’t
be treated as string
s, so you need to explicitly quote them:
value: "5"
value: "true"
Failing to escape these will result in errors from the API.
Conclusion
Hope this article has helped you specify environment variables more successfully.
(Cross posted to Google Cloud’s Medium publication).