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&param2=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 split K=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 the gcloud command.

So in this article I’ll give you several tips to avoid thinking about this:

  1. Do not cram all environment variables into a single option.
  2. Prevent your shell (e.g. bash) from interpreting arguments as much as you can.
  3. 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:

  1. specify all environment variables you need again, with --set-env-vars.
  2. 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&param2=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 strings, 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).