In this final installment of our blog series on migrating from Jenkins to GitHub Actions, we’ll explore how to set up a centralized CI/CD workflow that runs on our customized self-hosted runners. This centralized approach simplifies the management of multiple repositories by triggering workflows from a central location.
Prerequisites
Before implementing the centralized CI/CD workflow, ensure the following are in place:
- Self-hosted GitHub Actions Runners: These runners must be configured in your Kubernetes cluster to execute the workflows. You can follow GitHub’s documentation on sharing runners across your organization.
- Repository Permissions: Both the triggering and centralized repositories need appropriate access to each other to run workflows. Ensure you have configured repository permissions correctly, and shared workflows can inherit necessary secrets. Learn more here.
- Helm and Docker: Ensure Docker is installed and running on your runner and Helm is set up for managing deployments to Kubernetes.
- Secrets Setup: You need to configure the secrets required for Docker authentication (such as Docker credentials) and any necessary environment-specific secrets, which will be shared between the workflows. For guidance on sharing secrets, refer to GitHub’s documentation on workflow secrets.
Reference Documentation
- Creating Workflow Templates for Your Organization
- Sharing Actions and Workflows with Your Organization
- Reusing Workflows
Trigger Workflow
The trigger workflow is defined in the main repository, and it uses the workflow_call
event to call the centralized workflow from another repository. Here’s how it works:
name: Trigger Build and Deploy
on:
push:
branches:
- main
workflow_dispatch:
inputs:
repo-name:
description: 'Repository name to build and deploy'
required: true
type: string
branch:
description: 'Branch to deploy from'
required: true
type: string
default: 'main'
environment:
description: 'Environment for deployment'
required: true
type: string
default: 'dev'
jobs:
call-workflow:
uses: your-org/central-repo/.github/workflows/centralized-workflow.yml@main
with:
repo-name: ${{ inputs.repo-name }}
branch: ${{ inputs.branch }}
environment: ${{ inputs.environment }}
secrets: inherit
Explanation of Steps:
- Trigger: This workflow is triggered on a push to the
main
branch or via manual dispatch (with theworkflow_dispatch
event), allowing you to choose custom inputs such as the repository name, branch, and environment. - Job Execution: It calls the
centralized-workflow.yml
in a different repository using theuses
keyword. This passes inputs and secrets from the triggering workflow to the centralized one.
Centralized Workflow
The centralized workflow is stored in a separate repository, defining reusable CI/CD steps such as building the code, creating a Docker image, and deploying it using Helm.
name: Centralized Build and Deploy
on:
workflow_call:
inputs:
repo-name:
description: 'Repository name to build'
required: true
type: string
branch:
description: 'Branch for deployment'
required: true
type: string
default: 'main'
environment:
description: 'Deployment environment'
required: true
type: string
default: 'dev'
jobs:
build:
runs-on: self-hosted-runner
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
repository: your-org/${{ inputs.repo-name }}
ref: ${{ inputs.branch }}
- name: Build project
run: |
./gradlew build
- name: Push Docker image
run: |
docker build -t gcr.io/your-project/${{ inputs.repo-name }}:latest .
docker push gcr.io/your-project/${{ inputs.repo-name }}:latest
deploy:
runs-on: helm-runner
needs: build
steps:
- name: Deploy using Helm
run: |
helm upgrade --install ${{ inputs.repo-name }} helm-chart/ \
--namespace ${{ inputs.environment }} \
--set image=gcr.io/your-project/${{ inputs.repo-name }}:latest
Explanation of Steps:
- Triggering via
workflow_call
: This workflow is designed to be called by other repositories. Inputs such asrepo-name
,branch
, andenvironment
are passed in when the workflow is invoked. - Build Job:
- Checkout Code: The workflow checks out the source code from the repository specified in the
repo-name
input, using the branch specified inbranch
. - Build: It runs a Gradle build (
./gradlew build
) on the checked-out code. You can customize this step to fit your project’s build system. - Push Docker Image: A Docker image is built from the code and pushed to Google Artifact Registry. The image is tagged with the latest commit or a custom tag.
- Checkout Code: The workflow checks out the source code from the repository specified in the
- Deploy Job:
- Helm Deployment: After building and pushing the Docker image, the deploy job installs or upgrades the application in the specified Kubernetes environment using Helm. The Docker image is referenced by its location in Artifact Registry (
gcr.io
).
- Helm Deployment: After building and pushing the Docker image, the deploy job installs or upgrades the application in the specified Kubernetes environment using Helm. The Docker image is referenced by its location in Artifact Registry (
Secret Inheritance
To manage sensitive information such as Docker credentials or Helm configuration, we make use of secret inheritance. Secrets configured in the triggering repository are automatically passed to the centralized workflow using the secrets: inherit
statement. This ensures that the sensitive data required for tasks like authenticating with Google Artifact Registry or deploying to Kubernetes is shared securely between the workflows.
Conclusion
With this centralized CI/CD setup, we’ve significantly reduced duplication and improved scalability across multiple repositories. It simplifies the CI/CD process by consolidating workflows and ensuring consistency across various projects.
In the upcoming final blog of this series, I’ll show you how to fine-tune this workflow and provide real-world examples of its execution.