flake-update-20260505

Tasks Workflow

Creating and managing Tekton Tasks - the fundamental building blocks of Tekton Pipelines.

When to Use

  • Creating new Tasks
  • Defining Steps and workspaces
  • Parameterizing tasks for reusability
  • Emitting Results
  • Understanding TaskRuns

Task Anatomy

Basic Task Structure

apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: my-task
  namespace: default
spec:
  description: |
    A brief description of what this task does
  params:
    - name: param-name
      type: string
      description: Description of the parameter
      default: "default-value"
  workspaces:
    - name: workspace-name
      description: Workspace for source code
      mountPath: /workspace/source
  results:
    - name: result-name
      description: Description of the result
  steps:
    - name: step-name
      image: alpine:latest
      script: |
        #!/bin/sh
        echo "Hello from step"

Complete Task Example

apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: build-app
  labels:
    app.kubernetes.io/version: "1.0"
spec:
  description: Build application using Maven
  params:
    - name: maven-image
      type: string
      description: Maven image to use
      default: maven:3.8-openjdk-17
    - name: goals
      type: array
      description: Maven goals to execute
      default:
        - clean
        - package
    - name: settings-path
      type: string
      description: Path to Maven settings.xml
      default: ""
  workspaces:
    - name: source
      description: Source code workspace
    - name: maven-cache
      description: Maven local repository cache
      optional: true
  results:
    - name: artifact-name
      description: Name of the built artifact
    - name: build-timestamp
      description: When the build completed
  steps:
    - name: build
      image: $(params.maven-image)
      workingDir: $(workspaces.source.path)
      script: |
        #!/bin/bash
        set -e

        # Use cache if available
        if [ "$(workspaces.maven-cache.bound)" == "true" ]; then
          export MAVEN_OPTS="-Dmaven.repo.local=$(workspaces.maven-cache.path)"
        fi

        # Add settings if provided
        SETTINGS_ARG=""
        if [ -n "$(params.settings-path)" ]; then
          SETTINGS_ARG="-s $(params.settings-path)"
        fi

        # Run Maven build
        mvn $SETTINGS_ARG $(params.goals[*])

        # Output results
        ARTIFACT=$(ls target/*.jar | head -n 1 | xargs basename)
        echo -n "$ARTIFACT" > $(results.artifact-name.path)
        echo -n "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > $(results.build-timestamp.path)
      resources:
        requests:
          cpu: 500m
          memory: 1Gi
        limits:
          cpu: 2000m
          memory: 4Gi

Parameters

Parameter Types

String parameter:

params:
  - name: git-url
    type: string
    description: Git repository URL
    default: ""

Array parameter:

params:
  - name: build-args
    type: array
    description: Build arguments
    default: []

Object parameter (Beta):

params:
  - name: gitconfig
    type: object
    description: Git configuration
    properties:
      username:
        type: string
      email:
        type: string
    default:
      username: ""
      email: ""

Using Parameters in Steps

steps:
  - name: use-params
    image: alpine
    script: |
      echo "String param: $(params.git-url)"
      echo "Array param: $(params.build-args[*])"
      echo "Array first element: $(params.build-args[0])"
      echo "Object param: $(params.gitconfig.username)"

Parameter Validation

params:
  - name: environment
    type: string
    description: Deployment environment
    default: "dev"
    # Validation happens at runtime via when expressions or script logic

Workspaces

Declaring Workspaces

workspaces:
  - name: source
    description: Source code directory
    mountPath: /workspace/source  # Optional custom mount path
  - name: cache
    description: Build cache
    optional: true  # Task can run without this workspace
  - name: config
    description: Configuration files
    readOnly: true  # Workspace is read-only

Workspace Variables

script: |
  # Path to workspace
  echo "Workspace path: $(workspaces.source.path)"

  # PVC name (if using PVC)
  echo "PVC name: $(workspaces.source.claim)"

  # Check if workspace is bound
  if [ "$(workspaces.source.bound)" == "true" ]; then
    echo "Workspace is available"
  fi

Workspace Use Cases

PVC workspace (shared data):

# In TaskRun
workspaces:
  - name: source
    persistentVolumeClaim:
      claimName: shared-workspace

emptyDir workspace (temporary):

# In TaskRun
workspaces:
  - name: temp
    emptyDir: {}

ConfigMap workspace (configuration):

# In TaskRun
workspaces:
  - name: config
    configMap:
      name: app-config

Secret workspace (credentials):

# In TaskRun
workspaces:
  - name: credentials
    secret:
      secretName: git-credentials

Steps

Step Structure

steps:
  - name: step-name
    image: container-image
    command:
      - /bin/sh
    args:
      - -c
      - echo "Hello"
    env:
      - name: ENV_VAR
        value: "value"
    workingDir: /workspace
    script: |
      #!/bin/sh
      echo "Multi-line script"
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: 500m
        memory: 512Mi
    securityContext:
      runAsNonRoot: true
      runAsUser: 1000

Multiple Steps (Sequential)

steps:
  - name: fetch-dependencies
    image: node:18
    script: |
      npm install

  - name: run-tests
    image: node:18
    script: |
      npm test

  - name: build
    image: node:18
    script: |
      npm run build

Step Script vs Command

Using script (recommended):

steps:
  - name: build
    image: maven:3.8
    script: |
      #!/bin/bash
      set -e
      mvn clean package

Using command/args:

steps:
  - name: build
    image: maven:3.8
    command:
      - mvn
    args:
      - clean
      - package

Environment Variables

steps:
  - name: build
    image: alpine
    env:
      # From parameter
      - name: GIT_URL
        value: $(params.git-url)

      # From secret
      - name: API_KEY
        valueFrom:
          secretKeyRef:
            name: api-credentials
            key: api_key

      # From configMap
      - name: CONFIG_PATH
        valueFrom:
          configMapKeyRef:
            name: app-config
            key: config_path

      # Built-in Tekton variables
      - name: TASK_NAME
        value: $(context.task.name)
      - name: TASKRUN_NAME
        value: $(context.taskRun.name)
      - name: TASKRUN_NAMESPACE
        value: $(context.taskRun.namespace)

Results

Emitting Results

IMPORTANT: Results are limited to 4KB. Use workspaces for larger data.

results:
  - name: commit-sha
    description: Git commit SHA
  - name: image-digest
    description: Container image digest
  - name: test-status
    description: Test pass/fail status

steps:
  - name: get-commit
    image: alpine/git
    script: |
      git rev-parse HEAD > $(results.commit-sha.path)

  - name: build-image
    image: gcr.io/kaniko-project/executor
    script: |
      # Kaniko outputs digest to /kaniko/digest
      cp /kaniko/digest $(results.image-digest.path)

  - name: run-tests
    image: node:18
    script: |
      if npm test; then
        echo -n "passed" > $(results.test-status.path)
      else
        echo -n "failed" > $(results.test-status.path)
      fi

Result Size Considerations

Good (small data):

  • Commit SHAs
  • Image digests
  • Version numbers
  • Status flags
  • Ephemeral namespace names

Bad (use workspaces instead):

  • Full build logs
  • Large JSON payloads
  • File contents
  • Binary data

Resource Management

Step Resources

steps:
  - name: build
    image: maven:3.8
    resources:
      requests:
        cpu: 500m
        memory: 1Gi
      limits:
        cpu: 2000m
        memory: 4Gi

Task-Level Resources (Alpha)

# In TaskRun, not Task definition
apiVersion: tekton.dev/v1beta1
kind: TaskRun
spec:
  taskRef:
    name: build-task
  computeResources:
    requests:
      cpu: "1"
      memory: "2Gi"
    limits:
      cpu: "2"
      memory: "4Gi"

Best Practices:

  • Always set resource requests for scheduler
  • Set limits 1.5-2x higher than requests
  • Monitor actual usage with kubectl top pods
  • Use task-level resources for simplicity when all steps have similar requirements

Security Context

Task Security

apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: secure-task
spec:
  steps:
    - name: build
      image: alpine
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        runAsGroup: 1000
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop:
            - ALL
      volumeMounts:
        - name: tmp
          mountPath: /tmp
  volumes:
    - name: tmp
      emptyDir: {}

TaskRun

Creating TaskRun

Imperative (tkn):

tkn task start my-task \
  -p param1=value1 \
  -p param2=value2 \
  -w name=source,claimName=my-pvc \
  --showlog

Declarative (YAML):

apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
  name: my-taskrun
spec:
  taskRef:
    name: my-task
  params:
    - name: param1
      value: "value1"
    - name: param2
      value: "value2"
  workspaces:
    - name: source
      persistentVolumeClaim:
        claimName: my-pvc
  serviceAccountName: pipeline-sa
  timeout: 1h

TaskRun with Resolvers

Hub resolver:

apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
  name: git-clone-run
spec:
  taskRef:
    resolver: hub
    params:
      - name: name
        value: git-clone
      - name: version
        value: "0.10.0"
  params:
    - name: url
      value: https://github.com/myorg/myrepo
  workspaces:
    - name: output
      emptyDir: {}

Cluster resolver:

apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
  name: shared-task-run
spec:
  taskRef:
    resolver: cluster
    params:
      - name: name
        value: shared-task
      - name: namespace
        value: tekton-tasks

TaskRun Status

# View taskrun status
kubectl get taskrun my-taskrun

# Describe for details
kubectl describe taskrun my-taskrun

# Get status conditions
kubectl get taskrun my-taskrun -o jsonpath='{.status.conditions}'

# Get results
kubectl get taskrun my-taskrun -o jsonpath='{.status.taskResults}'

Reusable Task Patterns

Git Clone Task

apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: git-clone-custom
spec:
  params:
    - name: url
      description: Git repository URL
    - name: revision
      description: Branch, tag, or commit
      default: "main"
    - name: depth
      description: Shallow clone depth
      default: "1"
  workspaces:
    - name: output
      description: Clone destination
  steps:
    - name: clone
      image: alpine/git:latest
      script: |
        git clone --depth=$(params.depth) \
          --branch=$(params.revision) \
          $(params.url) \
          $(workspaces.output.path)

Test Runner Task

apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: run-tests
spec:
  params:
    - name: test-command
      description: Command to run tests
      default: "npm test"
  workspaces:
    - name: source
      description: Source code
  results:
    - name: test-result
      description: Test pass/fail
  steps:
    - name: test
      image: node:18
      workingDir: $(workspaces.source.path)
      script: |
        if $(params.test-command); then
          echo -n "passed" > $(results.test-result.path)
        else
          echo -n "failed" > $(results.test-result.path)
          exit 1
        fi

Best Practices

Task Design

  • Keep tasks focused on single responsibility
  • Use parameters for configurability (no hardcoded values)
  • Document all parameters, workspaces, and results
  • Emit results for small data (<4KB)
  • Use workspaces for sharing larger data
  • Set resource requests/limits for all steps
  • Use non-root containers when possible

Reusability

  • Check Tekton Hub before creating custom tasks
  • Make tasks generic and parameterized
  • Use descriptive names and labels
  • Version your tasks (labels/annotations)
  • Write comprehensive descriptions

Security

  • Run as non-root user
  • Use read-only root filesystem when possible
  • Drop unnecessary capabilities
  • Use workspace-based authentication
  • Avoid embedding secrets in task definitions

Performance

  • Use caching workspaces (Maven, npm, Go)
  • Set appropriate resource limits
  • Use specific image tags (not :latest)
  • Minimize image sizes (alpine, distroless)
  • Use build caches (Kaniko, Buildah)

Common Patterns

Conditional Execution

steps:
  - name: deploy-to-prod
    image: kubectl
    script: |
      if [ "$(params.environment)" = "production" ]; then
        kubectl apply -f production.yaml
      else
        echo "Skipping production deployment"
      fi

Error Handling

steps:
  - name: build-with-retry
    image: maven:3.8
    script: |
      #!/bin/bash
      set -e

      MAX_RETRIES=3
      RETRY_COUNT=0

      until mvn clean package; do
        RETRY_COUNT=$((RETRY_COUNT+1))
        if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then
          echo "Build failed after $MAX_RETRIES attempts"
          exit 1
        fi
        echo "Build failed, retrying ($RETRY_COUNT/$MAX_RETRIES)..."
        sleep 5
      done

Using Previous Step Results

steps:
  - name: get-version
    image: alpine
    script: |
      echo -n "1.2.3" > /tmp/version

  - name: use-version
    image: alpine
    script: |
      VERSION=$(cat /tmp/version)
      echo "Building version: $VERSION"

Troubleshooting

Task Won’t Start

# Check task exists
kubectl get task my-task

# Check taskrun status
kubectl describe taskrun my-taskrun

# Check events
kubectl get events --sort-by=.metadata.creationTimestamp

Step Failing

# View logs
tkn taskrun logs my-taskrun -f

# Check specific step
kubectl logs <pod-name> -c step-<step-name>

# Check previous container logs if crashed
kubectl logs <pod-name> -c step-<step-name> --previous

Workspace Issues

# Check PVC exists and is bound
kubectl get pvc

# Check workspace binding in taskrun
kubectl get taskrun my-taskrun -o jsonpath='{.spec.workspaces}'

# Check pod volumes
kubectl get pod <pod-name> -o jsonpath='{.spec.volumes}'

Results Not Available

# Check result was written
kubectl get taskrun my-taskrun -o jsonpath='{.status.taskResults}'

# Check step completed successfully
kubectl get taskrun my-taskrun -o jsonpath='{.status.steps}'

# Verify result size (<4KB)
kubectl logs <pod-name> -c step-<step-name> | wc -c