Serverless CI/CD with Travis CI, Serverless Framework and IBM Cloud Functions
Tags: serverless, cicd, deployments, node
Published: 2019-05-07 10:34:46

---
title: "Serverless CI/CD with Travis CI, Serverless Framework and IBM Cloud Functions"
published: true
description: "Setting up a continuous delivery deployment pipeline for a serverless application using Travis CI, The Serverless Framework on IBM Cloud Functions."
tags: [serverless, cicd, deployments, node.js]
canonical_url: http://jamesthom.as/blog/2019/04/23/serverless-ci-slash-cd-with-travis-ci-serverless-framework-and-ibm-cloud-functions/
---

How do you set up a [CI/CD pipeline](https://dzone.com/articles/what-is-cicd) for serverless applications? 

This blog post will explain how to use [Travis CI](https://travis-ci.org/), [The Serverless Framework](https://github.com/serverless/serverless) and the [AVA testing framework](https://github.com/avajs/ava) to set up a fully-automated build, deploy and test pipeline for a serverless application. It will use a real example of a production [serverless application](https://github.com/jthomas/openwhisk-release-verification), built using [Apache OpenWhisk](http://openwhisk.incubator.apache.org/) and running on [IBM Cloud Functions](https://console.bluemix.net/openwhisk/). The CI/CD pipeline will execute the following tasks...

- **Run project unit tests.**
- **Deploy application to test environment.**
- **Run acceptance tests against test environment.**
- **Deploy application to production environment.**
- **Run smoke tests against production environment.**

Before diving into the details of the CI/CD pipeline setup, let's start by showing the example serverless application being used for this project...

## Serverless Project - [http://apache.jamesthom.as/](http://apache.jamesthom.as/)

The "[Apache OpenWhisk Release Verification](https://github.com/jthomas/openwhisk-release-verification)" project is a serverless web application to help committers verify release candidates for the open-source project. It automates running the verification steps from the [ASF release checklist](https://cwiki.apache.org/confluence/display/OPENWHISK/How+to+verify+the+release+checklist+and+vote+on+OpenWhisk+modules+under+Apache) using serverless functions. Automating release candidate validation makes it easier for  committers to participate in release voting.

![Apache OpenWhisk Release Verification Tool](https://raw.githubusercontent.com/jthomas/openwhisk-release-verification/master/release-verification-tool.gif)

The project consists of a static web assets (HTML, JS, CSS files) and HTTP APIs. Static web assets are hosted by Github Pages from the [project repository](https://github.com/jthomas/openwhisk-release-verification). HTTP APIs are implemented as Apache OpenWhisk [actions](https://github.com/apache/incubator-openwhisk/blob/master/docs/actions.md) and exposed using the [API Gateway](https://github.com/apache/incubator-openwhisk/blob/master/docs/apigateway.md) service. [IBM Cloud Functions](https://console.bluemix.net/openwhisk/) is used to host the Apache OpenWhisk application.

No other cloud services, like databases, are needed by the backend. Release candidate information is retrieved in real-time by parsing the [HTML page](https://dist.apache.org/repos/dist/dev/incubator/openwhisk/) from the ASF website.

![Serverless Architecture](http://jamesthom.as/images/ow_release_verifier/architecture.png)

### Configuration

[The Serverless Framework](https://github.com/serverless/serverless) (with the [Apache OpenWhisk provider plugin](https://github.com/serverless/serverless-openwhisk)) is used to define the serverless functions used in the application. HTTP endpoints are also defined in the YAML configuration file.

```yaml
service: release-verfication

provider:
  name: openwhisk
  runtime: nodejs:10

functions:
  versions:
    handler: index.versions
    events:
      - http: GET /api/versions
  version_files:
    handler: index.version_files
    events:
      - http:
          method: GET
          path: /api/versions/{version}
          resp: http
...

plugins:
  - serverless-openwhisk
```

The framework handles all deployment and configuration tasks for the application. Setting up the application in a new environment is as simple as running the `serverless deploy` [command](https://github.com/serverless/serverless).

### Environments

Apache OpenWhisk uses [namespaces](https://github.com/apache/incubator-openwhisk/blob/master/docs/reference.md#fully-qualified-names) to group individual packages, actions, triggers and rules. Different namespaces can be used to provide isolated environments for applications.

IBM Cloud Functions automatically creates [user-based namespaces](https://cloud.ibm.com/docs/openwhisk?topic=cloud-functions-cloudfunctions_cli#region_info) in platform instances. These auto-generated namespaces mirror the IBM Cloud organisation and space used to access the instance. Creating [new spaces within an organisation](https://cloud.ibm.com/docs/account?topic=account-orgsspacesusers#cf-org-concepts) will provision extra namespaces.

I'm using a custom organisation for the application with three different spaces: **dev**, **test** and **prod**.

**dev** is used as a test environment to deploy functions during development. **test** is used by the CI/CD pipeline to deploy a temporary instance of the application during acceptance tests. **prod** is the production environment hosting the external application actions.

### Credentials

The [IBM Cloud CLI](https://cloud.ibm.com/docs/cli?topic=cloud-cli-install-ibmcloud-cli) is used to handle IBM Cloud Functions credentials. [Platform API keys](https://cloud.ibm.com/docs/iam?topic=iam-manapikey) will be used to log in the CLI from the CI/CD system.

When Cloud Functions CLI commands are issued (after targeting a new region, organisation or space), API keys for that Cloud Functions instance are automatically retrieved and stored locally. The Serverless Framework knows how to use these local credentials when interacting with the platform.

### High Availability?

The Apache OpenWhisk Release Verifier is not a critical cloud application which needs "[five nines](https://en.wikipedia.org/wiki/Five_nines)" of availability. The application is idle most of the time. It does not need a [highly available](https://en.wikipedia.org/wiki/High_availability) serverless architecture. This means the build pipeline does not have to...

- [Deploy application instances in multiple cloud regions.](https://cloud.ibm.com/docs/tutorials?topic=solution-tutorials-multi-region-serverless#multi-region-serverless)
- [Set up a global load balancer between regional instances.](https://www.ibm.com/blogs/bluemix/2019/04/load-balancing-api-calls-across-regions-with-ibm-cloud-internet-services-and-cloud-api-gateway/)
- Support "[zero downtime deploys](https://www.martinfowler.com/bliki/BlueGreenDeployment.html)" to minimise downtime during deployments.
- Automatic roll-back to previous versions on production issues.

New deployments will simply overwrite resources in the production namespace in a single region. If the production site is broken after a deployment, the smoke tests should catch this and email me to fix it!

## Testing

Given this tool will be used to check release candidates for the open-source project, I wanted to ensure it worked properly! Incorrect validation results could lead to invalid source archives being published.

I've chosen to rely heavily on unit tests to check the core business logic. These tests ensure all validation tasks work correctly, including PGP signature verification, cryptographic hash matching, LICENSE file contents and other ASF requirements for project releases.

Additionally, I've used end-to-end acceptance tests to validate the HTTP APIs work as expected. HTTP requests are sent to the API GW endpoints, with responses compared against expected values. All available release candidates are run through the validation process to check no errors are returned.

### Unit Tests

[Unit tests](https://en.wikipedia.org/wiki/Unit_testing) are implemented with the [AVA testing framework](https://github.com/avajs/ava). Unit tests live in the `unit/test/` [folder](https://github.com/jthomas/openwhisk-release-verification/tree/master/test/unit).

The `npm test` command alias runs the `ava test/unit/` command to execute all unit tests. This command can be executed locally, during development, or from the CI/CD pipeline.

```bash
$ npm test

> release-verification@1.0.0 test ~/code/release-verification
> ava test/unit/

 27 tests passed
```

### Acceptance Tests

[Acceptance tests](https://en.wikipedia.org/wiki/Acceptance_testing) check API endpoints return the expected responses for valid (and invalid) requests. Acceptance tests are executed against the API Gateway endpoints for an application instance.

The hostname used for HTTP requests is controlled using an environment variable (`HOST`). Since the same test suite test is used for acceptance and smoke tests, setting this environment variable is the only configuration needed to run tests against different environments.

API endpoints in the test and production environments are exposed using different custom sub-domains (`apache-api.jamesthom.as` and `apache-api-test.jamesthom.as`). NPM [scripts are used](https://github.com/jthomas/openwhisk-release-verification/blob/master/package.json#L8-L9) to provide commands (`acceptance-test` & `acceptance-prod`) which set the environment hostname before running the test suite.

```javascript
"scripts": {
    "acceptance-test": "HOST=apache-api-test.jamesthom.as ava -v --fail-fast test/acceptance/",
    "acceptance-prod": "HOST=apache-api.jamesthom.as ava -v --fail-fast test/acceptance/"
  },
```

```
$ npm run acceptance-prod

> release-verification@1.0.0 acceptance-prod ~/code/release-verification
> HOST=apache-api.jamesthom.as ava -v --fail-fast  test/acceptance/

  ✔ should return list of release candidates (3.7s)
    ℹ running api testing against https://apache-api.jamesthom.as/api/versions
  ✔ should return 404 for file list when release candidate is invalid (2.1s)
    ℹ running api testing against https://apache-api.jamesthom.as/api/versions/unknown
  ...

  6 tests passed
```

Acceptance tests are also implemented with the AVA testing framework. All acceptance tests live in a single [test file](https://github.com/jthomas/openwhisk-release-verification/blob/master/test/acceptance/api.js) (`unit/acceptance/api.js`).

## CI/CD Pipeline

When new commits are pushed to the `master` branch on the project repository, the following steps needed to be kicked off by the build pipeline…

- *Run project unit tests.*
- *Deploy application to test environment.*
- *Run acceptance tests against test environment.*
- *Deploy application to production environment.*
- *Run smoke tests against production environment.*

If any of the steps fail, the build pipeline should stop and send me a notification email.

### Travis

[Travis CI](https://travis-ci.org/) is used to implement the CI/CD build pipeline. Travis CI uses a [custom file](https://github.com/jthomas/openwhisk-release-verification/blob/master/.travis.yml) (`.travis.yml`) in the project repository to configure the build pipeline. This YAML file defines commands to execute during each phase of build pipeline. If any of the commands fail, the build will stop at that phase without proceeding.

*Here is the completed `.travis.yml` file for this project: https://github.com/jthomas/openwhisk-release-verification/blob/master/.travis.yml*

I'm using the following Travis CI [build phases](https://docs.travis-ci.com/user/job-lifecycle#the-job-lifecycle) to implement the pipeline: **install**, **before_script**, **script**, **before_deploy** and **deploy**. Commands will run in the Node.js 10 build environment, which pre-installs the language runtime and package manager.

```yaml
language: node_js
node_js:
  - "10"
```

#### install

In the `install` [phase](https://github.com/jthomas/openwhisk-release-verification/blob/master/.travis.yml#L5-L9), I need to set up the build environment to deploy the application and run tests. 

This means installing the IBM Cloud CLI, [Cloud Functions CLI plugin](https://cloud.ibm.com/openwhisk/learn/cli), The Serverless Framework (with Apache OpenWhisk plugin), application test framework (AvaJS) and other project dependencies.

The IBM Cloud CLI is installed using a shell script. Running a CLI sub-command installs the [Cloud Functions plugin](https://cloud.ibm.com/openwhisk/learn/cli).

The Serverless Framework is installed as global NPM package (using `npm -g install`). The Apache OpenWhisk provider plugin is handled as [normal project dependency](https://github.com/jthomas/openwhisk-release-verification/blob/master/package.json#L13-L23), along with the test framework. Both those dependencies are installed using NPM.

```yaml
install:
  - curl -fsSL https://clis.cloud.ibm.com/install/linux | sh
  - ibmcloud plugin install cloud-functions
  - npm install serverless -g
  - npm install
```

#### before_script

This [phase](https://github.com/jthomas/openwhisk-release-verification/blob/master/.travis.yml#L11-L16) is used to run unit tests, catching errors in core business logic, before setting up credentials (used in the `script` phase) for the acceptance test environment. Unit test failures will halt the build immediately, skipping test and production deployments.

Custom variables provide the API key, platform endpoint, organisation and space identifiers which are used for the test environment. The CLI is authenticated using these values, before running the `ibmcloud fn api list` command. This ensures Cloud Functions credentials are available locally, as used by The Serverless Framework.

```yaml
before_script:
  - npm test
  - ibmcloud login --apikey $IBMCLOUD_API_KEY -a $IBMCLOUD_API_ENDPOINT
  - ibmcloud target -o $IBMCLOUD_ORG -s $IBMCLOUD_TEST_SPACE
  - ibmcloud fn api list > /dev/null
  - ibmcloud target
```

#### script

With the build system configured, the application can be deployed to test environment, followed by running acceptance tests. If either deployment or acceptance tests fail, the build will stop, skipping the production deployment.

Acceptance tests use an environment variable to configure the hostname test cases are executed against. The `npm run acceptance-test` alias command sets this value to the test environment hostname (`apache-api-test.jamesthom.as`) before running the test suite.

```yaml
script:
  - sls deploy
  - npm run acceptance-test
```

#### before_deploy

Before deploying to production, Cloud Functions credentials need to be updated. The IBM Cloud CLI is used to target the production environment, before running a Cloud Functions CLI command. This updates local credentials with the production environment credentials.

```yaml
before_deploy:
  - ibmcloud target -s $IBMCLOUD_PROD_SPACE
  - ibmcloud fn api list > /dev/null
  - ibmcloud target
```

#### deploy

If all the proceeding stages have successfully finished, the application can be deployed to the production. Following this final deployment, smoke tests are used to check production APIs still work as expected.

Smoke tests are just the same acceptance tests executed against the production environment. The `npm run acceptance-prod` alias command sets the hostname configuration value to the production environment  (`apache-api.jamesthom.as`) before running the test suite.

```yaml
deploy:
  provider: script
  script: sls deploy && npm run acceptance-prod
  skip_cleanup: true
```

*Using the `skip_cleanup` parameter leaves installed artifacts from previous phases in the build environment. This means we don't have to re-install the IBM Cloud CLI, The Serverless Framework or NPM dependencies needed to run the production deployment and smoke tests.*

### success?

If all of the [build phases](https://travis-ci.org/jthomas/openwhisk-release-verification) are successful, the latest project code should have been deployed to the production environment. 💯💯💯

![Build Screenshoot](http://jamesthom.as/images/build-screenshot.png)

If the build failed due to unit test failures, the test suite can be ran locally to fix any errors. Deployment failures can be investigated using the console output logs from Travis CI. Acceptance test issues, against test or production environments, can be debugged by logging into those environments locally and running the test suite from my development machine.

## Conclusion

Using Travis CI with The Serverless Framework and a JavaScript testing framework, I was able to set up a fully-automated CI/CD deployment pipeline for the Apache OpenWhisk release candidate verification tool.

Using a CI/CD pipeline, rather than a manual approach, for deployments has the following advantages...

- No more manual and error-prone deploys relying on a human 👨‍💻 :)
- Automatic unit & acceptance test execution catch errors before deployments.
- Production environment only accessed by CI/CD system, reducing accidental breakages.
- All cloud resources must be configured in code. No "[snowflake](https://martinfowler.com/bliki/SnowflakeServer.html)" environments allowed.

Having finished code for new project features or bug fixes, all I have to do is push changes to the GitHub repository. This fires the Travis CI build pipeline which will automatically deploy the updated application to the production environment. If there are any issues, due to failed tests or deployments, I'll be notified by email.

This allows me to get back to adding new features to the tool (and fixing bugs) rather than wrestling with deployments, managing credentials for multiple environments and then trying to remember to run tests against the correct instances!