How Codefresh Helps us at Kubevious Building CI/CD Pipelines and Running Critical Release Processes
Ruben Hakopian
Ruben Hakopian
September 05, 2020
/
5 minutes read

How Codefresh Helps us at Kubevious Building CI/CD Pipelines and Running Critical Release Processes

In 2020 there is no shortage of CI/CD providers, and one might think that they are all the same. While top-level capabilities are mostly overlapping, the details and design choices of a particular CI/CD tool define how it would automate critical business processes. That was probably less of a big deal ten years back with monoliths because such pipelines need to be created only once (often outsourced) and rarely modified afterward. Things changed a lot with microservices and going Cloud Native. Such approaches produce lots of moving parts that need to be continuously validated and deployed. The CI/CD tool would not only need to support those capabilities but also enable ease of use to create, debug, and modify numerous pipelines.

In this article, we will tell about our CI/CD journey at Kubevious and describe specific examples of how Codefresh helps up along the way.

Codefresh Pipeline

Containers

There is no surprise that we start with Containers. Fortunately in Codefresh containers are first-class citizens. Every single step in the pipeline runs inside a container. Codefresh already comes with a pretty robust set of step types to clone repositories, build and publish docker images, send a message to Slack channel, or an SMS through Twilio, validating Kubernetes configuration with Octarine, and many more. If that’s not sufficient, it is also possible to run a step with any arbitrary docker image using freestyle steps to execute shell commands of your choice.

Building Images

We are using multistage docker files, and the majority of build logic is concentrated there. That provides absolute independence of the build environment. We are also using the same process during active development.

Here is an example of a Dockerfile we use to build a react based frontend. As you can see, it is self-contained, eliminating the need to pull dependencies and doing any other preparation.

############################################################################### # Step 1: Builder image FROM kubevious/react-builder:12 as build WORKDIR /app ENV NODE_ENV production ENV PATH /app/node_modules/.bin:$PATH COPY src/package.json ./ COPY src/package-lock.json ./ RUN npm ci --only=production COPY src/ ./ RUN npm run build ############################################################################### # Step 2: Runner image FROM kubevious/nginx:1.8 COPY nginx/default.conf /etc/nginx/conf.d/ COPY --from=build /app/build /usr/share/nginx/html

The simple Codefresh build step below would do the job. We explicitly disable image push to the registry and postpone the process until the tests are passed.

build_image: title: "Building Docker Image" type: build image_name: "kubevious/ui" stage: build disable_push: true

Once the image is marked as good, it is pushed to the registry using the push step:

push_image: title: "Pushing image to DockerHub" type: push stage: build registry: dockerhub candidate: "${{build_image}}" tags: - latest - "${{DOCKER_TAG}}"

We are using a GitOps process for all pipelines. A secondary repository contains build metadata, such as version number and some other deployment details. We use a freestyle step to inject and bump versions before running the build step.

Running Complex Tests

Running unit tests in pipelines is a relatively simple task and can be achieved using freestyle steps. But running anything more complex (such as integration tests, UI automation using Cypress, etc.) requires significant support from the CI/CD provider. This is one of the areas where Codefresh shines. Service containers allow bringing up container dependencies necessary to run tests. That is a very powerful concept that takes care of the initial launch and allows running readiness probes to trigger tests only after dependencies are up and healthy.

In this example below, we are launching MySQL inside a composition to run npm tests.

run_tests: title: Run Tests stage: test image: 'kubevious/node-builder:12' working_directory: "${{clone}}" environment: - "MYSQL_HOST=mysql" - "MYSQL_DB=sample-db" - "MYSQL_USER=root" - "MYSQL_PASS=" commands: - npm test services: composition: mysql: image: mysql:8.0.19 ports: - 3306 environment: - "MYSQL_DATABASE=sample-db" - "MYSQL_ALLOW_EMPTY_PASSWORD=yes" command: --default-authentication-plugin=mysql_native_password readiness: image: mysql:8.0.19 timeoutSeconds: 90 volumes: - './${{CF_REPO_NAME}}/mysql:/my-init-script' commands: - "mysql --host=mysql --user=root --password= sample-db -e \"show databases;\"" - "mysql --host=mysql --user=root --password= sample-db < /my-init-script/init.sql" - "mysql --host=mysql --user=root --password= sample-db -e \"show tables;\""

Another example is to run the React UI in a container and validate using Cypress tests. Here we are using the same docker image produced by the build step above.

run_ui_test: title: Test UI stage: validate-mock image: cypress/included:4.8.0 working_directory: ${{main_clone}}/src services: composition: ui: image: ${{build_image}} ports: - 80 environment: - "BACKEND_URL=localhost:4001" readiness: image: 'curlimages/curl:7.70.0' timeoutSeconds: 30 commands: - "curl http://ui:80" commands: - npm run cy:run

Parallel Execution

Time is everything. Shaving off minutes from each run accumulates to hours, days, and weeks saved in total. Sometimes the only way to do that is to split long-lasting steps into substeps that can run parallel. Tests, uploads, and downloads or large files and images are excellent candidates for parallel execution.

Codefresh allows two ways of achieving parallelism. The easy way is to define a special parallel step that allows forking and executing inner sub-steps in parallel. In most cases, it’s a no brainer task and requires wrapping steps into a type: parallel step. The example below splits test suite execution into two groups that run in parallel.

run_tests: type: parallel steps: run_tests_part_1: title: Running Test Part 1 working_directory: ${{clone}} commands: - mocha -g tests_1 run_tests_part_2: title: Running Test Part 2 working_directory: ${{clone}} commands: - mocha -g tests_2

A more complicated yet powerful approach allows total parallelism of the pipeline. In parallel pipeline mode, it is required to define dependencies on each step explicitly. The step execution order is determined dynamically based on dependencies. Such an approach allows complete control of execution. See complete documentation on writing advanced parallel workflows.

Another really cool capability is generating pipeline steps from the matrix of input parameters such as images, environment variables, commands, etc. The matrix allows effortless mass production of pipeline steps. One use case could be triggering parallel deployment to all public cloud regions. Learn more about matrix parallel steps here.

Documentation

The documentation is well written and up to date. Docs include lots of examples covering the majority of use cases. Most of the pipelines can be produced by just copy-pasting from docs and replacing some parameters and names.

Support

We are super happy with the technical and customer support at Codefresh. As the product evolves, the need for more complicated cases increases. While most of the things we could find in docs, for some cases a personalized help from the Codefresh team was irreplaceable.

Conclusion

The CI/CD is an integral part of any software company. While the healthy operation of CI/CD tools allows one to forget about its existence completely, any hiccups are very painful and prevent the business from running. Codefresh integration was smooth and fits very well with our Cloud Native stack. There is so much more to Codefresh that was not even mentioned in this article. Pipeline caching, security scanning, live debugging, running pipelines locally, Kubernetes integration and other capabilities can help get most of the Codefresh and bring CI/CD pipelines to the next level.

message