7

This comes up from time to time in several of our dev teams, without us having figured out the "right" way:

We use a lot of react-based webapplications that "compile" into static websites that are just a few html, js and css files.

However, the "building" of these apps take a number of variables that enable/disable feature flags, configure backend urls, etc. This means that we cannot "build" a binary in the traditional sense and just apply a config file at deploy-time - the "build" itself needs to have these environment-specific variables set, and thus the only time we can "build" is at deploy-time.

For now we solve this by injecting the required environment variables into the Docker container and run a start cmd along the lines of

npm build && nginx run

This has a couple of disadvantages:

  1. The build process takes a lot of cpu/memory relative to the run-time requirements of the container. That means we need to scale the container for the build process instead of the run-time requirements - which feels wrong
  2. Build failures are hard to "track". We can use healthchecks in Kubernetes, but if a build takes 2 minutes we still have to wait for 3 mins (1 extra for safety) before we can start testing the container's healthcheck endpoint to see if its alive.
  3. Deployments might take a long time: If we configure Kubernetes to do a "serial" deployment, it will start each pod and wait for the "initialDelay" period of 2-3 minutes before starting the next. This means that we're easily looking at a 10-min deploy time if the deployment is scaled to 3-4 pods.

This all feels very suboptimal to me. I'd be very interested in hearing how the community solves the "build at deploy-time" conundrum with modern javascript webapps.

I realize that for Kubernetes we could use "init-containers" that perform the build, places the artiacts in persistent storage and then have the app containers simply pull from persistent storage during startup, but this still feels more like "bypassing" the problem than solving the root issue.

  • Sorry, I don't understand - why are you running npm build at runtime instead of during the container build? – Xiong Chiamiov Feb 2 '18 at 15:41
  • @XiongChiamiov I assume frontend configuration is done during the build and is not modifiable without rebuilding (I'd argue it should be separated in an included file which, at worst, could be 'sed' at container startup) – Tensibai Feb 2 '18 at 15:47
  • exactly. npm build is just an arbitrary command. Could be react build or sanity build or whatever the JS framework expects. We're forced to do it at container deploy because that's when we know the environment to build for. – Trondh Feb 2 '18 at 21:20
5

From my standpoint the best approach would be to:

  1. Separate build process using Jenkins that would build NodeJS project into distribution and wrap it into Docker image
  2. Spin up Docker registry that would accumulate Docker images from Jenkins (this registry should be accessible from Kubernetes cluster)
  3. Move environment variables to Jenkins secrets or use separate tool to collect and combine configurations from external Git repo (we use Spring Cloud Config through REST API to collect json/yml definitions for every application in every environment)

Using Jenkins you can configure continuous delivery system based on generic Pipelines. Possible flow would be:

  1. Developers finish their work in correspondence to GitFlow (latest Pull Request merged into Release branch)
  2. Webhook triggers Jenkins pipeline:
    • Stage 1: collect environment definition
    • Stage 2: build NodeJS app using npm
    • Stage 3: build Nginx Docker image with distribution
    • Stage 4: push Docker image to Docker registry
    • Stage 5: deploy/update service in Kubernetes using standard definitions
  3. Notifications send in regard of results

This process might be visualized using Rancher. I can answer your questions in chat.

  • So you're proposing to have one image per environment ? That's a workaround consuming a bunch of space for just some variables... – Tensibai Feb 2 '18 at 13:28
  • @Tensibai u r talking about ~15mb images per each environment. With rotation and additional janitor service 1 Gb would be enough to handle all projects. – Maksim Feb 2 '18 at 14:34
  • Based on this solution, how would you produce multiple "configs" of an app, for example one for "staging" and one for "prod"? (lets pretend that there's a setting called "BackendApi" that differs, for instance. – Trondh Feb 2 '18 at 14:35
  • @Trondh as I said we use Spring Cloud Config. What it does is for each application it gets basic config and applies additional configurations from profiles. E.g. if you request https://config/backend.yml you'll receive configuration from application.yml and additional properties from backend.yml. For https://config/backend-stage.yml it will respond with properties applied by application.yml <- backend.yml <- backend-stage.yml. – Maksim Feb 2 '18 at 14:41
  • @Maksim 15MB for a few bytes of text is a waste, at some point with hundreds of microservices on various environment with various version on them it will become TB of data for the same few bytes at origin. That's a wrong path in my opinion if I got right what you're proposing, as you can't ensure the image in QA and prod is really the same as they are from two builds (even with same code, they won't be the same id and validation becomes a mess). But maybe I got it rong and you have only one image and configuration at spin up of the pod, if it is so I didn't understood your post properly. – Tensibai Feb 2 '18 at 14:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.