CI/CD with Nomad

Hello folks!
I’ve setup Nomad & Consul clusters and now exploring options for CI/CD.
I have following setup:

  • private Git repository hosted on gitlab.com
  • private Docker registry
  • Nomad cluster is in private VPC and sits behind load balancer. Nomad/Consul endpoints are not exposed to the world, only respective webapps ports

At the moment my flow is looking as following:

  • I push code
  • GitLab runs tests and builds Docker image
  • GitLab pushes Docker image to the private registry
  • I’m picking new image and manually update my Nomad Job using the UI

I want to automate this flow. Whats tools will suite better to help me given all limitations?
I considering following options:

  • Setup Waypoint and move build from GitLab to waypoint, but I don’t like it because I want to have my pipelines in the GitLab
  • Setup some background service to periodically poll GitLab and redeploy stuff when image is built
3 Likes

nomad itself will do most of this for you. If data or variables change from one deploy to the next, a nomad plan / apply would pick this up and apply the changes for you.

Doing things like this would allow you to keep the investment in Gitlab pipelines (and perhaps even help clean them up or obsolete many steps).

If you take advantage of the update stanza, you can even implement directly useful continuous deployment concepts like canary checks and blue/green deployment.

As long as the API can be reached by a nomad client on a runner in your pipeline, you can plan / apply changes in a pipeline

I would think just using Nomad is the easiest way to deliver changes, as long as you can trigger a nomad plan / apply when the conditions change, e.g. when an image for a task changes.

Curious to know if this is a useful approach. I too was attracted to Waypoint when it landed:

but for me it still feels like it’s from the future.

1 Like

I’m using GitHub Actions but I’m sure something like this can be done in GitLab.

I run this on self-hosted runners to not to make nomad api public.

jobs:
  nomad:
    name: "Run jobs"
    runs-on: self-hosted

    strategy:
      fail-fast: false
      matrix:
        job: "${{ fromJson(inputs.jobs) }}"

    env:
      NOMAD_ADDR: "http://localhost:4646"
      NOMAD_NAMESPACE: "somenamespace"
      NOMAD_TOKEN: "token"

    defaults:
      run:
        working-directory: "jobs/${{ matrix.job.dir }}"

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Check connection
        run: nomad node status

      - name: Validate job
        run: nomad validate job.nomad.hcl

      - name: Plan job
        run: |
          echo "Run nomad plan"
          if out="$(nomad plan job.nomad.hcl)"; then
            echo "$out"
          else
            # Plan will return one of the following exit codes:
            # * 0: No allocations created or destroyed.
            # * 1: Allocations created or destroyed.
            # * 255: Error determining plan results.
            case $? in
              255) echo "$out"; exit 1 ;;
              1) echo "$out"; exit 0 ;;
            esac
          fi
      - name: Run job
        run: nomad run job.nomad.hcl

nomad run job.nomad.hcl will exit on succesful deploy or will timeout on depending on update stanza settings or CI/CD settings.

One thing that I miss is the ability to run and then pull separately (useful when running batch jobs, as they exit immediately), but it is easy to write a script that does that.

4 Likes

@rozhok in v0.10 of Waypoint we launched a Tech Preview of pipelines in Waypoint! We’d love to get your feedback on it to see if it addresses your use case. :slight_smile:

1 Like

hi,

we using Gitlab too … and I use for deploy and check “levant”. I’ve added some more tools on the Image, like curl / jq / http_check (from monitoring-plugins) and also nomad.

Every projects has a nomad/foo.nomad jobfile, with all what is needed and that is copied via artifact to the deploy stage. Only what isn’t nice … if you have oneshot containers (like for Django and collectstatic) … the pipeline will always fail, because not all containers are up. I try to catch with with a different way and use curl to check the status …

cu denny

1 Like

Hi,

Can anybody post your gitlabci.yaml so that it would help others?

Hello @lovestaco

My Gitlab ci.yaml is works like this:

  • This may not be best practice, and I get the token from vault when running Nomad.
stages:       
  - gradle_build
  - docker_build
  - vault_token_inject
  - test
  - deploy

variables:
  ecr_url: ecr_image_url
  ecr_login: ecr_url

#import
include:
  - template: Jobs/SAST.gitlab-ci.yml

#import use
semgrep-sast:
  stage: test 
  tags:
    - my-runner


build-job-gradle:
  image: openjdk:17-jdk-alpine
  stage: gradle_build
  script: |
    cd app/01-kv-static/
    chmod +x ./gradlew
    ./gradlew clean build
    #pwd
    ls -l build/libs/*.jar
  artifacts:
    paths:
      - app/01-kv-static/build/libs/*.jar
    expire_in: 1 hour
  tags:
    - my-runner

build-job-docker:   # This job runs in the test stage.
  stage: docker_build    # It only starts when the job in the build stage completes successfully.  
  before_script: |
    aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $ecr_login
  script: |
    cp app/01-kv-static/build/libs/*.jar Docker/
    docker build -t $ecr_url:$CI_COMMIT_SHORT_SHA Docker/
    docker images |grep $ecr_url
    docker push $ecr_url:$CI_COMMIT_SHORT_SHA    
  dependencies:
    - build-job-gradle   
  tags:
    - my-runner

vault-integrate:
  stage: vault_token_inject
  image: hashicorp/vault:latest  
  id_tokens:
    VAULT_AUTH_TOKEN:
      aud: my_gitlab_url
  script: |
    export VAULT_ADDR=my_vault_url
    export VAULT_NAMESPACE=admin
    export VAULT_TOKEN="$(vault write -field=token auth/gitlab-jwt/login role=gitlab-role jwt=$VAULT_AUTH_TOKEN)"
    export NOMAD_DEPLOY_TOKEN="$(vault read -field=secret_id nomad/creds/nomad_role)"

    echo "export NOMAD_TOKEN=$NOMAD_DEPLOY_TOKEN" > nomad.token
  artifacts:
    paths:
      - nomad.token    
  tags:
    - my-runner     

nomad-run-job:      # This job runs in the deploy stage.
  stage: deploy  # It only runs when *both* jobs in the test stage complete successfully.
  #environment: production
  image: registry.gitlab.com/internetarchive/nomad/master
  script: |
    source nomad.token

    cd nomad_job
    cat <<EOF > dev.env
    spring_image = "$ecr_url:$CI_COMMIT_SHORT_SHA"
    ecr_login = "$ecr_login"
    spring_port = 8081
    vault_policy = "vault_and_spring"
    env = "dev"
    dc = "dc1"
    EOF
    
    export NOMAD_ADDR=my_nomad_url

    nomad job validate -var-file="dev.env" vault_spring.nomad 

    nomad run -var-file="dev.env" vault_spring.nomad 
  tags:
    - my-runner  

I hope this helps.