Building Docker Actions with Aktion

Sebastien Goasguen

Sebastien Goasguen

May 22, 2019
Building Docker Actions with Aktion
Building Docker Actions with Aktion

In a previous article Aktion was released to the world. In that announcement we described how Aktion brings GitHub Actions to Kubernetes by using TektonCD Pipeline.

Given a GitHub Action workflow, Aktion will convert it into Tekton API objects: Pipeline, PipelineResources, Tasks and optional TaskRun. These objects are custom resources that the Tekton controller understands and transforms ultimately into Kubernetes Pods to perform, well, tasks. You can get a crash course on Tekton by following the introduction tutorial.

A simple hello world Action workflow would resemble something along the lines of:

workflow "Tekton test" {
  on = "push"
  resolves = [
    "First Action",
  ]
}

action "First Action" {
  uses = "docker://centos"
  runs = "echo"
  args = "Hello world"
}

Each action is required to specify a uses entry to denote the Docker container to run the specified command in. In the initial release of Aktion, the only supported entry is a pre-existing Docker image using the docker:// prefix. GitHub, however, allows for the specification of a local path inside a repository in the form of ./path/to/Dockerfile, or an external GitHub repository in the form of organization/project/path/to/Dockerfile. At GitHub, Actions take this path, and builds the container on the fly for you.

This means that Aktion also needs to be able to trigger a Docker image build. This technical post shows you how we added this feature and used Tekton objects to perform a container build.

Enter Aktion

Aktion was recently updated to enable support for the repo specific portion of referring to a Docker container. To support this, some fundamental changes have been made to how Aktion creates the TektonCD Pipeline CRDs:

1. The --repo flag has been replaced by a --git flag, and is now required for actions that utilize the ./path/to/Dockerfile local path approach.
1. Build tasks are now created to convert each uses component into a Docker container for use within the action.
1. Each action still corresponds to a step within a Pipeline Task and each Workflow is a corresponding Pipeline Task. With the introduction of the docker container build tasks, the tasks are now contained within a TektonCD Pipeline.
1. --taskrun has been renamed --pipelinerun and will create a TektonCD PipelineRun CRD.

Looking at a sample action:

workflow "local repo test" {
  on = "push"
  resolves = [
    "First Action",
  ]
}

action "First Action" {
  uses = "./samples/test-images"
  runs = "echo"
  args = "Hello world"
}

Inside the samples/test-images directory is a Dockerfile:

# A test image to verify the flow of Aktion

FROM alpine:latest

RUN echo "This is a test run"

With Aktion, you can translate the entire workflow into Tekton Objects like so:

$ aktion create -f samples/test-local.workflow --pipelinerun --git github.com/triggermesh/aktion

You will clearly see below that the HCL syntax of GitHub Action is much more terse and user friendly, while the Tekton objects in YAML are not meant for human consumption. We foresee that Action will be used heavily from a user standpoint but that Tekton will become the backend of choice for workflows.

For reference here are all the objects created by the transformation:

---
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
  creationTimestamp: null
  name: samples-test-images-git
spec:
  params:
  - name: revision
    value: master
  - name: url
    value: github.com/triggermesh/aktion
  type: git
status: {}
---
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
  creationTimestamp: null
  name: samples-test-images-image
spec:
  params:
  - name: url
  value: knative.registry.svc.cluster.local/samples-test-images-image
  type: image
status: {}
---
apiVersion: tekton.dev/v1alpha1
kind: Task
metadata:
  creationTimestamp: null
  name: build-samples-test-images
spec:
  inputs:
    params:
    - default: Dockerfile
      name: pathToDockerFile
    - name: pathToContext
    resources:
    - name: workspace
      targetPath: ""
      type: git
  outputs:
  resources:
  - name: image
    targetPath: ""
    type: image
  steps:
  - args:
  - --dockerfile=${inputs.params.pathToDockerFile}
  - --destination=${outputs.resources.image.url}
  - --context=${inputs.params.pathToContext}
  - --insecure
  - --insecure-registry
  - --verbosity=debug
  command:
    - /kaniko/executor
  image: gcr.io/kaniko-project/executor
  name: build-and-push-samples-test-images
resources: {}
---
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
  creationTimestamp: null
  name: local-repo-test
spec:
  params:
  - name: revision
  value: master
  - name: url
  value: github.com/triggermesh/aktion
  type: git
status: {}
---
apiVersion: tekton.dev/v1alpha1
kind: Task
metadata:
  creationTimestamp: null
  name: local-repo-test
spec:
  inputs:
  resources:
  - name: local-repo-test
    targetPath: ""
  type: git
  steps:
  - args:
  - Hello
  - world
  command:
  - echo
  image: knative.registry.svc.cluster.local/samples-test-images-image
  name: first-action
resources: {}
---
apiVersion: tekton.dev/v1alpha1
kind: Pipeline
metadata:
  creationTimestamp: "2019-05-03T22:58:00Z"
  name: local-repo-test-pipeline
spec:
  params: null
  resources:
  - name: samples-test-images-git
    type: git
  - name: samples-test-images-image
    type: image
  - name: local-repo-test
    type: git
tasks:
  - name: build-samples-test-images
    params:
    - name: pathToContext
      value: /workspace/workspace/samples/test-images
    resources:
      inputs:
      - name: workspace
        resource: samples-test-images-git
      outputs:
      - name: image
      resource: samples-test-images-image
taskRef:
name: build-samples-test-images
- name: local-repo-test
resources:
inputs:
- name: local-repo-test
resource: local-repo-test
outputs: null
taskRef:
name: local-repo-test
status: {}
---
apiVersion: tekton.dev/v1alpha1
kind: PipelineRun
 metadata:
  creationTimestamp: "2019-05-03T22:58:00Z"
  name: local-repo-test-pipeline-run
spec:
Status: ""
params: null
pipelineRef:
name: local-repo-test-pipeline
resources:
- name: samples-test-images-image
resourceRef:
name: samples-test-images-image
- name: samples-test-images-git
resourceRef:
name: samples-test-images-git
- name: local-repo-test
resourceRef:
name: local-repo-test
serviceAccount: ""
trigger:
type: manual
status:
conditions: null

The breakdown on the components:

PipelineResources corresponding to the input Github repo and output image
– A build Task using [Kaniko](https://github.com/GoogleContainerTools/kaniko)
– A third PipelineResource for the git repo to be referenced within the main task
– A main Task that is referencing the image built from the previous build task
– A Pipeline CRD that populates the appropriate references between the build
task and main task
– A PipelineRun CRD to perform the one-shot execute operation

To see the above example in operation on a Kubernetes cluster using TriggerMesh’s Kubernetes based Docker Registry. You can pipe the resulting objects via kubectl apply . Two pods will be created: one to build the image, and a second one to run the main task.

Applying the Tekton objects leads to:

$ ./aktion create -f samples/test-local.workflow --pipelinerun --git https://github.com/triggermesh/aktion|kubectl apply -f -
pipelineresource.tekton.dev/samples-test-images-git created
pipelineresource.tekton.dev/samples-test-images-image created
task.tekton.dev/build-samples-test-images created
pipelineresource.tekton.dev/local-repo-test created
task.tekton.dev/local-repo-test created
pipeline.tekton.dev/local-repo-test-pipeline configured
pipelinerun.tekton.dev/local-repo-test-pipeline-run created

You will be able to verify the status of the build using:

kubectl get pipelinerun.tekton.dev/local-repo-test-pipeline-run
NAME STATUS STARTTIME COMPLETIONTIME
local-repo-test-pipeline-run True 52s 16s

The pods created will be similar to:

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
local-repo-test-pipeline-run-build-samples-test-images-s7mfh-pod-fe1332 0/3 Completed 0 110s
local-repo-test-pipeline-run-local-repo-test-7p9mx-pod-09a983 0/3 Completed 0 109s

Lastly, verifying the logs from local-repo-test-pipeline-run-local-repo-test-7p9mx-pod-09a983 which is the main task:

$ kubectl logs local-repo-test-pipeline-run-local-repo-test-7p9mx-pod-09a983 build-step-first-action
Hello world

To rerun the workflow, delete the pipelinerun.tekton.dev/local-repo-test-pipeline-run entity, and reapply the PipelineRun YAML component.

Aktion and remote GitHub repos

GitHub Action also supports referencing GitHub repos as well. Here is a sample workflow that references a remote repository: https://github.com/cab105/aktion-test.

workflow "local repo test" {
  on = "push"
  resolves = [
    "Cat hello",
    "Echo hello",
  ]
}

action "Cat hello" {
  uses = "cab105/aktion-test/hello-repo/images@hello-repo"
  runs = "cat"
  args = "/hello.txt"
}

action "Echo hello" {
  uses = "cab105/aktion-test/hello-repo/images@hello-repo"
  runs = "echo"
  args = "Hello from Aktion"
}

In this case, uses reflects the organization of cab105 and repo name of aktion-test followed by the path that contains a Dockerfile. The @hello-repo indicates the branch to find this directory, but can also reflect the repo tag or changeset ID.

Aktion does require the file workflow file to be present. To try this out, clone the repository https://github.com/cab105/aktion-test and checkout the hello-repo branch.

Since there are no local directories specified in this workflow (the uses arg starting with ./), the --git flag can be omitted.

To create the corresponding Tekton objects do:

$ aktion create -f ./hello-repo.workflow --pipelinerun

Enjoy

Don’t be overwhelmed by the face full of YAML syndrome. Keep in mind what this all means: You can execute GitHub Actions on your own Kubernetes cluster !!!! Think about this …

Create your first event flow in under 5 minutes