Giter Club home page Giter Club logo

decap's Introduction

History

Mark: How hard can it be to build a build server?

Josh: How hard can space travel be? But no matter. So why not use a pure Kubernetes backend?

Overview

Decap is a CI build server based on a Kubernetes backend that executes shell script-based builds in a specially provided build pod. The backend is a containerized webapp that manages build pods, and the frontend a single page app that provides a friendly UX. Builds are executed in pods spun up on demand on a per-build basis, with build results published to S3 buckets and a DynamoDb table.

Decap was originally conceived as a headless build server, driven solely by post commit hooks from userland build repositories. It now has a frontend, but the name stuck.

This project is under active development, and has no releases yet.

Theory of Operation

You have projects you want to build. Your builds are articulated in terms of userland shell scripts. Decap ships with a base build container that mounts your build scripts as a git repository and locates them by a team/libary convention in the container filesystem.

Either user-initiated builds or post commit hooks sent from your projects of interest drive HTTP requests to the containerized Decap webapp. This webapp in turn makes calls to the Kubernetes API master to launch an ephemeral build pod to build a single instance of your code. Once the build is finished the pod exits, saving no build pod state from one build to the next. Build results are shipped to Amazon AWS S3 buckets and a DynamoDb table.

Your build scripts are completely free form. Here are two examples:

#!/bin/bash

echo hello world
#!/bin/bash

git clone https://git.example.com/repo --branch ${BRANCH_TO_BUILD}
mvn clean install
mvn deploy

Sidecar build containers

If your build needs additional services, such as MySQL, RabbitMQ, etc., Decap provides a way to ingest a set of additional container specs into the Kubernetes build Pod descriptor. This means those services will be available to your build at localhost:port.

AWS Setup

Decap uses AWS S3 buckets to store build artifacts and console logs, and DynamoDb to store overall build results and metadata.

To run the scripts below that create these AWS resources, you must have installed the AWS Command Line Client. For instructions on how to do this, see http://aws.amazon.com/documentation/cli/.

jq is also installation requirement, and is needed to parse certain outputs of the Amazon aws command line tool.

IAM user

In ./aws-resources we provide shell scripts for creating the AWS resources Decap needs. To run these scripts effectively, you will need an AWS account with what we're calling root like powers. This account must be capable of creating the following types of resources on AWS:

  • AWS IAM users
  • Access credentials
  • S3 buckets
  • DynamoDb tables
  • Policies

Your main AWS Dashboard account should have these powers. If it does not, contact your AWS administrator.

N.B. Your Dashboard account is used only in a one-time capacity to create these resources. Thereafter, Decap is configured to use these resources, including the created IAM user decap. Your Dashboard account is not used by Decap at runtime.

One time admin configuration

Using a profile name decapadmin, add your AWS Dashboard account Access Key ID and Secret Access Key to $HOME/.aws/credentials file. Add a default region to $HOME/.aws/config.

$HOME/.aws/credentials:

[decapadmin]
aws_access_key_id = (your Dashboard key)
aws_secret_access_key = (your Dashboard secret)

$HOME/.aws/config:

[decapadmin]
region=us-west-1

Create the AWS resources Decap requires

$ cd aws-resources
$ sh create-world.sh

You should now be able to view the following resources in your AWS Dashboard UI:

  • an IAM user named decap
  • a set of access credentials for use by the newly created decap user, written to the file aws.credentials
  • two S3 buckets: decap-console-logs and decap-build-artifacts
  • one DynamoDb table named decap-build-metadata
  • five policies attached to the user decap, which are required for access to the S3 buckets and DynamoDb table

The five policies are named:

  • decap-db-base
  • decap-db-isBuilding
  • decap-db-projectKey
  • decap-s3-build-artifacts
  • decap-s3-console-logs

Kubernetes Cluster Setup

Bring up a Kubernetes cluster as appropriate: https://github.com/kubernetes/kubernetes/tree/master/docs/getting-started-guides. Decap requires SkyDNS, which is included if you use the standard kube-up.sh script to create your cluster.

Namespaces

Decap requires two Kubernetes namespaces: decap and decap-system. The decap namespace is where your build pods run. decap-system is where the containerized Decap webapp runs.

Create the Kubernetes namespaces required for Decap

$ kubectl create -f k8s-resources/decap-namespaces.yaml

Decap Kubernetes Secret for AWS and Github credentials

The AWS Access Key and Secret for user decap created above allows the build pod to upload build artifacts and console logs to S3, and to write build information to the DynamoDb table. The Access Key and Secret also allows the Decap webapp to access these same buckets and table.

To be most effective, Decap also needs access to the list of branches for your various projects. Decap can query your project repositories for this branch information. Without access to your project branch information, Decap's web UI cannot offer to build a particular branch on your projects. For Github projects, this means Decap needs an OAuth2 Github ClientID and ClientSecret. Generate Github OAuth2 credentials here: https://github.com/settings/applications/new.

Using the AWS Access Key and Secret in ./aws-resources/aws.credentials, and your Github ClientID and ClientSecret, craft a k8s-resources/decap-secrets.yaml

apiVersion: v1
data:
  aws-key: thekey
  aws-secret: base64(thesecret)
  aws-region: base64(theregion)
  github-client-id: base64(github client-id)
  github-client-secret: base64(github client-secret)
kind: Secret
metadata:
     name: decap-credentials
type: Opaque

and create it on the Kubernetes cluster in both the decap and decap-system namespaces

$ kubectl --namespace=decap-system create -f k8s-resources/decap-secrets.yaml
$ kubectl --namespace=decap create -f k8s-resources/decap-secrets.yaml

The base build container will automatically have these Kubernetes Secrets mounted in both the build container and the webapp container. The base build container will use them for publishing build results. The webapp container will use them for querying AWS and Github.

Decap Kubernetes Pod creation

Create the pod that runs the Decap webapp in the cluster:

$ kubectl create -f k8s-resources/decap.yaml

Setting up a build scripts repository

Decap leverages Kubernetes's ability to mount a Git repository readonly inside a container. When you launch a build in the build container, Kubernetes will mount the build scripts repo that contains the build scripts for your projects. Here is a sample build script repository

https://github.com/ae6rt/decap-build-scripts

The build container refers to this repository as a mounted volume. Build scripts are indexed by project key by the build container entrypoint. For github based projects, the project key is the github username + "/" + repository name. Generally, the username is referred to as the team and the repository basename as the project For example, if the github username is ae6rt and the repository name is dynamodb-lab, then the project key is "ae6rt/dynamodb-lab". The build script is by convention named build.sh and is located relative to the top level of the build scipts repository at ae6rt/dynamodb-lab/build.sh.

The build container will call your project's build script, capture the console logs, and ship the build artifacts, console logs and build metadata to S3 and DynamoDb.

Project metadata files and branch information

Each project must have a project.json file placed on par with a project's build.sh script. Projects omitting the file will be ignored by Decap. project.json has the following format

{
     "buildImage": "ae6rt/decap-build-base:latest",
     "repoManager": "github",
     "managedRefRegex": "master|develop|issue/.*",
     "repoUrl": "https://github.com/ae6rt/dynamodb-lab.git",
     "repoDescription": "AWS DynamoDb lab"
}

All project.json files must have a buildImage field. Without it, decap cannot pull the container image in which to execute the build.

Knowing the repository manager and repository URL, Decap can query the repository manager for branches and tags on the project. Knowing the branches, the Decap web UI can offer to let the user build a particular branch on a project. Github is currently the only supported repository manager.

The project json descriptor contains an optional field managedRefRegex. If this field is present, only branches and tags that match the regex will be built as a result of post-commit hook handling of a project git-push. If this field is omitted, all refs are eligible to be built in the event of a post-commit push on the project. The managedRefRegex does not affect which branches can be built manually through the Decap web UI.

Sidecar build containers

TBD

Handling updates to the build scripts repository

Decap will refresh its representation of the build scripts repository if you add a post-commit hook to the build scripts repository. Point the post commit URL at Decap baseURL/hooks/buildscripts. Any HTTP POST to this endpoint will force a refresh of the build script repository in the Decap webapp.

Base Build Container Environment

Here is the base build container reference: https://github.com/ae6rt/decap/tree/master/build-container

The following environment variables are available in your build scripts:

  • BUILD_ID: UUID that uniquely identifies this build
  • PROJECT_KEY: a composite key consisting of your project team/project
  • BRANCH_TO_BUILD: an optional git branch for use with builds that can put it to use

Concurrent builds of a given team/project + branch are currently forbidden, and enforced with a lock in etcd, which runs in the same pod as the Decap webapp.

Build pod instances are given the following Kubernetes labels

"labels": {
   "type": "decap-build",
   "team": "{{.Team}}",
   "project": "{{.Project}}",
   "branch": "{{.BranchToBuild}}",
}

Developing Decap

The Decap source is divided into three parts:

  • Base Build Container in build-container/
  • Webapp in web/
  • Kubernetes resource configs in k8s-resources/

Base Build Container

This is the place to modify the base build container ENTRYPOINT script and Dockerfile

Webapp

This is a Go webapp that receives commit hooks from various repository managers. Upon receiving a hook on a managed project, Decap will launch a container to execute a build on the project and branch.

Kubernetes resource configs

This contains yaml files that describe Kubernetes resources Decap needs to function.

decap's People

Contributors

ae6rt avatar bluesunrise avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Forkers

ottogiron

decap's Issues

/teams looks broken

Thought I fixed this:

laptop:work> sh teams.sh 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    45  100    45    0     0    201      0 --:--:-- --:--:-- --:--:--   200
{
  "teams": [
    {
      "name": "ae6rt"
    },
    {
      "name": "ae6rt"
    }
  ]
}

get-builds endpoint is returning the wrong content-type

It should return application/json:

 curl -i http://192.168.99.100:31000/api/v1/builds/ae6rt/dynamodb-lab && echo

HTTP/1.1 200 OK
Access-Control-Allow-Headers: DECAP-APP-NAME, DECAP-API-TOKEN, Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With
Access-Control-Allow-Methods: GET, PUT, POST, DELETE, OPTIONS
Access-Control-Allow-Origin: http://localhost:9000
Date: Sun, 09 Apr 2017 15:01:26 GMT
Content-Length: 302
Content-Type: text/plain; charset=utf-8

{"builds":[{"id":"0df70cb1-c6ce-418f-9c80-8fe3fd234c79","projectKey":"ae6rt/dynamodb-lab","branch":"master","result":0,"duration":0,"startTime":1491571627},{"id":"99e4ed8a-92e8-45c7-b80d-4592b51a98e4","projectKey":"ae6rt/dynamodb-lab","branch":"master","result":0,"duration":0,"startTime":1491570756}]}

Define a policy to queue builds that are blocked by a currently running build

If a build cannot get a lock on its key, what should we do? Queue the build for later execution? Or do nothing?

If nothing, we may have been ignoring a post-commit hook's tacit request that the project be built. If we ignore it, we omit building new commits.

If we queue it, decap gets notably more complex, as it's now in the business of queues, perhaps with expiry policies on queued builds.

The deferral service needs a mutex

Protect critical sections of the deferral service with a mutex. One goroutine might be trying to remove deferred items via a frontend webapp action, while the resubmit-goroutine might be naturally deleting some of the same items at that moment.

Be selective in which branches a post commit hook is willing to build

Many organizations use branch names named after specific issues. For example, github feature branches might be named issue/1, or feature/issue-1. Bug fix branches might be named hotfix/issue-1, or bugfix/1. JIRA users often name branches after JIRA issues: feature/PLAT-999. These branches that have a strong naming convention are branches you want subject to continuous integration.

Moreover, developers often create experimental branches that are neither features or bugfixes. E.g, Bob working on an experimental feature might create branch bob-widgetx. Bob wants to build this branch on-demand, which the decap UI will allow. But he sees no need to have it built every time he pushes code to it.

Decap already recognizes a project.json file on par with a build.sh script. This project descriptor is where decap looks to see what repository manager (github, stash, bitbucket, et) to call to get a list of branches and tags on a project. See https://github.com/ae6rt/decap-build-scripts/blob/master/ae6rt/hello-world-java/project.json. In this descriptor, define a regex that constrains which branches will be built upon post commit hooks with a field named managed-branch-regex:

{
     "build-image": "ae6rt/java7:latest",
     "managed-branch-regex": "master|develop|issue/.*",
     "repo-manager": "github",
     "repo-url": "https://github.com/ae6rt/hello-world-java.git",
     "repo-description": "Hello world in Java"
}

For post commit hook build requests, do not launch a build unless the ref can be matched by the managed-branch-regex.

Use DynamoDB for locking builds

Deprecate etcd for storing build locks. DynamoDB can do this as well, and is a pure cloud solution. Abstract the lock interface so other implementations are possible.

Devise a way to regulate number of concurrent builds

A real k8s cluster with a known finite number of nodes cannot run an arbitrarily large number of concurrent builds. Devise a way to query the cluster for running builds and defer a build if a max has been reached

Add StorageService REST API to query the historical average build duration

For a given project P, provide a REST API endpoint that will return the average build time of historical builds. This gives Decap the ability to display a progress bar in the UI to give a rough idea of how long a build will take.

Implementing this should be straightforward, as its a single query to DynamoDb for the last dozen or so builds, averaging over the build-duration.

Use Amazon Simple Queue Service to store deferred builds

We had been using etcd v2 to store deferred builds in the order they were deferred. Deprecate this in favor of a default deferral queue built atop Amazon Simple Queue Service using a FIFO queue. Abstract with an interface so other impls are possible.

The build container cannot find its credentials

It's looking for Secrets in the wrong place.

$ kubectl --namespace=decap logs 3fa1034e-0329-40f5-83b8-36b0445227e6 -c build-server
BRANCH_TO_BUILD=master
BUILD_ID=3fa1034e-0329-40f5-83b8-36b0445227e6
BUILD_LOCK_KEY=ae6rt/dynamodb-lab/master
HOME=/root
HOSTNAME=3fa1034e-0329-40f5-83b8-36b0445227e6
KUBERNETES_PORT=tcp://10.0.0.1:443
KUBERNETES_PORT_443_TCP=tcp://10.0.0.1:443
KUBERNETES_PORT_443_TCP_ADDR=10.0.0.1
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_SERVICE_HOST=10.0.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PROJECT_KEY=ae6rt/dynamodb-lab
PWD=/
SHLVL=1
_=/usr/bin/env
+ sort
+ env
++ cat /var/run/kubernetes/secrets/aws-key
cat: /var/run/kubernetes/secrets/aws-key: No such file or directory
+ readonly AWS_ACCESS_KEY_ID=
+ AWS_ACCESS_KEY_ID=
++ cat /var/run/kubernetes/secrets/aws-secret
cat: /var/run/kubernetes/secrets/aws-secret: No such file or directory
+ readonly AWS_SECRET_ACCESS_KEY=
+ AWS_SECRET_ACCESS_KEY=
++ cat /var/run/kubernetes/secrets/aws-region
/home/decap/workspace /
hello from Github: build branch master
/
/build-artifacts /
cat: /var/run/kubernetes/secrets/aws-region: No such file or directory
+ readonly AWS_DEFAULT_REGION=
+ AWS_DEFAULT_REGION=
+ '[' 0 -eq 0 ']'
+ TAR=archive.tar
+ ARTIFACTS=/build-artifacts
+ WORKSPACE=/home/decap/workspace
+ CONSOLE=/tmp/console.log
++ date +%s
+ START=1491569382
+ pushd /home/decap/workspace
+ tee /tmp/console.log
+ sh /home/decap/buildscripts/decap-build-scripts/ae6rt/dynamodb-lab/build.sh
+ BUILD_EXITCODE=0
+ popd
++ date +%s
+ STOP=1491569382
+ DURATION=0
+ gzip /tmp/console.log
+ pushd /build-artifacts
+ tar czf /tmp/archive.tar.gz .
/
+ popd
+ bctool s3put --aws-access-key-id --aws-secret-access-key --aws-region --bucket-name decap-build-artifacts --build-id 3fa1034e-0329-40f5-83b8-36b0445227e6 --content-type application/x-gzip --filename /tmp/archive.tar.gz
2017/04/07 12:49:42 main.go:132: {

}
2017/04/07 12:49:42 main.go:133: EmptyStaticCreds: static credentials are empty
+ bctool s3put --aws-access-key-id --aws-secret-access-key --aws-region --bucket-name decap-console-logs --build-id 3fa1034e-0329-40f5-83b8-36b0445227e6 --content-type application/x-gzip --filename /tmp/console.log.gz
2017/04/07 12:49:42 main.go:132: {

}
2017/04/07 12:49:42 main.go:133: EmptyStaticCreds: static credentials are empty
+ bctool record-build-metadata --aws-access-key-id --aws-secret-access-key --aws-region --table-name decap-build-metadata --build-id 3fa1034e-0329-40f5-83b8-36b0445227e6 --project-key ae6rt/dynamodb-lab --branch master --build-start-time 1491569382 --build-duration 0 --build-result 0
2017/04/07 12:49:42 main.go:180: {

}
2017/04/07 12:49:42 main.go:181: InvalidParameter: 1 validation error(s) found.
- minimum field size of 3, PutItemInput.TableName.

package bctool in a Homebrew recipe

Users may need to manually unlock builds using bctool. Package in a homebrew recipe for easy installation on user desktops.

We might also consider extending bctool to do build-queue and log level toggling. If we do, we should rename it to something more general purpose (bc means build container). Perhaps decapctl, after kubectl.

Rename the top level Project data structure

This struct that fully describes a Project, including the owning Team, code, repo descriptor and sidecars

https://github.com/ae6rt/decap/blob/master/web/apiv1types.go#L20

type Project struct {
    Team       string            `json:"team"`
    Library    string            `json:"project"`
    Descriptor ProjectDescriptor `json:"descriptor,omitempty"`
    Sidecars   []string          `json:"sidecars,omitempty"`
}

should be renamed, as the existing Library field now has the meaning of "project". iow, weird variable naming and semantics will ensue if we consider the Team + Project tuple to be wrapped in a data structure also named Project.

Not sure what the new name for this struct should be, but it should be different from Project. When we figure out a new name for this struct, the Library field should be renamed to Project, as well as changing the httprouter keys for what now reads as library (https://github.com/ae6rt/decap/blob/master/web/main.go#L60)

Users won't see httprouter key names, but at some point I think we want a way to refer to the tuple Team + Project with a unique word in the Decap vocabulary.

implement metrics

This should be done after the go-kit analysis is done. go-kit easily admits middleware, which is where metrics would reside.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.