# Serverless v3.0.0

Cloud Native from source to production with semantic-release and conventional commits.

- **Author:** Patrick Heneise
- **Published:** 2021-11-23
- **Updated:** 2021-11-23
- **Tags:** continuous-integration, google-cloud-run, semantic-versioning, deployment-pipelines, continuous-delivery

---

import Image from '../../src/components/Image.astro'

## Breaking Changes: v2.0.0 to v3.0.0

Some time ago we wrote about
[Serverless deployments v2.0.0](/articles/serverless-v2-0-0/) and a lot has
changed and improved since then, so it's time for an update.

One of the biggest improvements to the workflow is
[GitHub Actions](https://github.com/features/actions). No need for external
CI/CD systems to generate new versions any more. GitHub also
[improved Releases](https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases),
and [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
reached v1.0.0.

## Continous Deployment of Serverless Applications

Let's walk through the details of a fully automated continous integration and
deployment system with all those fancy new toys. The one thing that hasn't
changed since the last article is our Git workflow: we recommend the
[Git Feature Branch Workflow](https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow).
The merge strategy doesn't matter much, rebase, squash and merge all work with
the following tools and flows.

### Use Conventional Commits

We use [Semantic Release](https://github.com/semantic-release/semantic-release)
to calculate version numbers based on commit messages. The 'de facto' standard
for these commit messages has been defined in
[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). In
short, your commit messages start with a keyword, a scope and then a short
description of the change. Commit messages should be written in present tense
("closes", not "closed"):

- `fix(accounts): calculation error on totals, closes #123` to create a
  `Patch Release` or
- `feat(users): create and update user details, closes #123` to create a
  `Minor Release`

You can find a
[quick summary here](https://www.conventionalcommits.org/en/v1.0.0/#summary).

### Add Semantic Release and GitHub Actions

Actions is a CI/CD product from GitHub. You can define "workflows" as simple
`yml` files to go through certain steps such as linting, testing, building etc.
They work for pretty much any project / programming language. Every Action
ususally has a trigger ("on") and jobs. You can read more about
[Events that trigger workflows](https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows),
[Building and testing Node.js Actions](https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs-or-python)
or even
[Custom JavaScript Actions](https://docs.github.com/en/actions/creating-actions/creating-a-javascript-action).

We usually use a simple "test, build & release" Action, for example for our
integration Actions like
[Cloudflare Preview URL](https://github.com/zentered/cloudflare-preview-url/blob/main/.github/workflows/publish.yml).

Below you can find a slightly more complex Action that restricts permissions and
runs checkout, npm install, linter, codestyle checks, tests, coverage reports
and semantic release. See
[Hardening GitHub Actions](/articles/hardening-github-actions-in-private-repositories/)
for more information about security/permission steps taken in this Action.

**./github/workflows/publish.yml**:

```yaml
name: Semantic Release

on:
  push:
    branches:
      - 'main'

permissions:
  actions: none
  checks: none
  contents: write
  deployments: write
  issues: read
  packages: none
  pull-requests: write
  repository-projects: none
  security-events: none
  statuses: none

jobs:
  publish:
    env:
      CI: true
      NODE_ENV: test

    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2.4.1
        with:
          node-version: '16'
          cache: 'npm'
      - name: Install Dependencies
        run: npm ci --ignore-scripts
      - name: Run Linter
        run: npm run lint
      - name: Validate Codestyle
        run: npm run codestyle
      - name: Test
        run: npm test
        env:
          DISABLE_REQUEST_LOGGING: true
          LOG_LEVEL: error
      - name: report coverage
        uses: codecov/codecov-action@v2.1.0
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
      - name: Semantic Release
        run: npx semantic-release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

The magic line to convert our Conventional Commit messages into Releases is
`run: npx semantic-release`. This will run semantic-release after the tests,
lint etc. are done and generate a release with fancy notes based on the commits
that have been merged. For example, if you currently have a `v1.2.0` deployed,
and merge a `fix`, semantic-release will publish `v1.2.1`.

You can
[see how Semantic Releases look like](https://github.com/zentered/cloudflare-preview-url/releases)
in our open source repos. The important piece here is, that each release also
comes with a `tag`. This is interesting and useful for a variety of reasons, for
example compliance ("what has been deployed when?"), and of course the developer
experience (DX). You can see the currently deployed version in each environment,
without checking cryptic 6-digit SHAs. The tag is also the ingredient required
for the next step:

### Automated Deployment

In [Google Cloud Build](https://cloud.google.com/build) (or
[AWS CodeBuild](https://docs.aws.amazon.com/codebuild/latest/userguide/create-project-console.html)
or
[Azure Pipelines](https://azure.microsoft.com/en-us/services/devops/pipelines/))
you can select a source repository for deployments and pick "tags" as a build
trigger. `.v*` for example will deploy every new release. If you want to deploy
a _Major_ release in a new environment, you can switch the previous trigger
settings to `.v1.*` to only deploy `v1` releases, but not `v2`. This is a nice
way to test breaking changes.

<Image
  src="/images/articles/2021-11/cloud-build-tag-trigger.png"
  alt="screenshot of cloud build trigger settings"
  width={565}
  height={498}
/>

Cloud Build allows you to continously deploy your application. On GCP this works
with a `cloudbuild.yml` file:

```yml
steps:
  - name: 'gcr.io/cloud-builders/docker'
    entrypoint: 'bash'
    args:
      - '-c'
      - |
        docker pull us-central1-docker.pkg.dev/$PROJECT_ID/api-demo/api:latest || exit 0
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - build
      - -t
      - us-central1-docker.pkg.dev/$PROJECT_ID/api-demo/api:$TAG_NAME
      - -t
      - us-central1-docker.pkg.dev/$PROJECT_ID/api-demo/api:latest
      - .
      - --cache-from
      - us-central1-docker.pkg.dev/$PROJECT_ID/api-demo/api:latest
  - name: 'gcr.io/cloud-builders/docker'
    args:
      ['push', 'us-central1-docker.pkg.dev/$PROJECT_ID/api-demo/api:$TAG_NAME']
  - name: 'gcr.io/cloud-builders/gcloud'
    args:
      - run
      - deploy
      - api-demo
      - --image=us-central1-docker.pkg.dev/$PROJECT_ID/api-demo/api:$TAG_NAME
      - --region=us-central1
      - --memory=256Mi
      - --platform=managed
      - --allow-unauthenticated
      - --min-instances=0
      - --max-instances=5
images:
  - 'us-central1-docker.pkg.dev/$PROJECT_ID/api-demo/api:$TAG_NAME'
  - 'us-central1-docker.pkg.dev/$PROJECT_ID/api-demo/api:latest'
timeout: 1800s
```

`$TAG_NAME` is the version number, so all your images and deployments have a
semantic version number and you can clearly identify what's running where. There
are four steps here:

1. fetch last image from cache (if there's a cached image)
2. build the code and Dockerfile
3. push the image to the registry
4. deploy the image on Cloud Run

_Note:_ We're using the new
[Artifact Registry](https://cloud.google.com/artifact-registry/) instead of the
deprecated Container Registry, so the registry urls are slightly different. You
can create
[your Artifact Repository here](https://console.cloud.google.com/artifacts/create-repo)
and adjust the registry region in the cloudbuild file.

### Go live on managed k8s

Google Cloud, AWS and Azure all have managed [Kubernetes](https://kubernetes.io)
products which are great to focus on software development and leave the k8s
configs, helm charts and autoscale yamls to the _DevOps Pros_. In the previous
section we defined a step to deploy to Cloud Run. For GCP, you may need to
enable the API, billing etc. on your project:

- enable [Billing](https://console.cloud.google.com/billing) (requires a credit
  card)
- enable the
  [Cloud Build API](https://console.cloud.google.com/apis/api/cloudbuild.googleapis.com/overview)
- enable the
  [Cloud Run Admin API](https://console.cloud.google.com/apis/api/run.googleapis.com/overview)
- enable the
  [Artifact Registry API](https://console.cloud.google.com/apis/api/artifactregistry.googleapis.com/overview)
- [create a Repository in the Artifact Registry](https://console.cloud.google.com/artifacts/create-repo)
  (Format: Docker, Region: your choice)

If the build is green, you should see a new service in the
[Cloud Run](https://cloud.google.com/run/) console. When you click on the
service, at the top you see the new public URL (ie. `app-name-123.run.app`) for
your service. Link a domain to that URL and 🎉, your API is now live.

## Summary

Let's recap a everyday workflow:

- Developer picks an issue and works on this amazing new feature for your
  product
- Developer pushes changes to a feature branch and opens a
  [Draft Pull Request](https://github.blog/2019-02-14-introducing-draft-pull-requests/).
  A Pull Request Preview is automatically deployed via the
  [GitHub/Google Cloud Build](https://github.com/apps/google-cloud-build)
  integration and changes can be tested
- Developer marks the Pull Request as "Ready for Review"
- Team reviews the PR, discusses the changes and approves (hopefully ;))
- Pull Request is merged into `main` by the developer or reviewer
- GitHub `publish.yml` Action is triggered ("push to main") and runs tests, lint
  etc.
- If everything goes well, `npx semantic-release` executes, calculating a new
  version number, summarizing the release notes and creates a new tag and
  release
- Google Cloud Build trigger is fired on "new tag", builds your app and pushes
  the Docker image to the Artifact Registry
- Cloud Build starts the Cloud Run deployment with the new image (which has the
  version number)
- Cloud Run starts the new service and changes the traffic allocation to the new
  instance
- The change is live, start over with the next feature 😎

Cloud Run enables you to split traffic between multiple revisions, so you can
perform gradual rollouts such as canary deployments or blue/green deployments.
Combined with automatic rollbacks, you can skip your staging environment
alltogether and continously deploy to production. Developers can easily revert
changes by merging a commit to `main`.

Thanks for reading! If you have any questions or comments, please
[reach out on Bluesky](https://bsky.app/profile/zentered.co) or
[start a discussion on GitHub](https://github.com/zentered/zentered/discussions/new?category=ideas-feedback).
