曦远 (DealiAxy)一份私人的文字刊物

StarBlog
返回文章列表折页手记阅读全文
DevOps

Continuous Integration Guide: Automated Build and Deployment of AspNetCore Projects Using GitHub Action

Another boring automation tutorial: GitHub Actions builds AspNetCore Docker image and pushes to Alibaba Cloud, then deploys with a local runner. Simplify the workflow? Who cares about these details? Proxy configuration is so annoying!

曦远 (DealiAxy)约 34 分钟
目录15 个章节

Foreword

Previously, I researched using GitHub Actions to automatically build and publish nuget packages: Developing Modern .NetCore Console Applications: (4) Using GitHub Actions for Automatic Building and Publishing nuget Packages

Now, let's take it a step further: use GitHub Actions to build Docker images within the provided runners, then submit them to Alibaba Cloud's private image registry, and finally pull the images down to run them on a local runner.

This article uses the AIHub project as an example.

AIHub is an AI platform that integrates multiple models, offering features such as large language models, computer vision, and natural language processing. It is developed based on AspNetCore + Blazor Server technology.

Preparing the Dockerfile

First, prepare the Dockerfile for building the image.

Generally, when using the dotnet cli to create a project, this Dockerfile can be generated automatically.

If it's not available, you can refer to my example below.

FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
COPY ["AIHub.Blazor/AIHub.Blazor.csproj", "AIHub.Blazor/"]
RUN dotnet restore "AIHub.Blazor/AIHub.Blazor.csproj"
COPY . .
WORKDIR "/src/AIHub.Blazor"
RUN dotnet build "AIHub.Blazor.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "AIHub.Blazor.csproj" -c Release -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "AIHub.Blazor.dll"]

You can try building it locally first to see if there are any issues.

docker build .

If everything is normal, you can proceed to the next step.

Creating a Private Image Repository

You can set one up yourself or use cloud service products. Here, I use Alibaba Cloud's "Container Registry (ACR)," and the personal edition is free.

Create an image repository, select "Local Repository" as the code source, and you can push images to the repository via the command line.

PS: I noticed there is also a GitHub repository as a code source inside, but I haven't tested it yet.

After creation, there will be an operation guide, which is not very important. We only need to focus on the username and password.

The username used for login is the full Alibaba Cloud account name, and the password is the one set when the service was activated.

You can modify the credential password on the Access Credential page.

OK, save this information well; it will be needed next.

The repository address used in this article is: registry.cn-hangzhou.aliyuncs.com/deali/aihub-test

Configure GitHub Action Secret

Configure the address, username, password, and other information of the Alibaba Cloud repository into Action Secret.

Here, the names I set are:

  • ALIYUN_DOCKER_REPO
  • ALIYUN_USERNAME
  • ALIYUN_PWD

Building the Image

Write the GitHub workflow configuration, the details will not be repeated here. For those who are unclear, you can refer to this article: Developing Modern .NetCore Console Applications: (4) Using GitHub Actions for Automatic Build and NuGet Package Publishing

GitHub Workflow

This is provided by GitHub. I haven't used this solution myself, but those interested can give it a try.

# This workflow uses unauthenticated GitHub actions.
# They are provided by third parties and are subject to
# Separate Terms of Service, Privacy Policy, and Support
# Documentation.
# GitHub recommends pinning actions to commit SHAs.
# To get a newer version, you need to update SHA.
# You can also reference tags or branches, but this operation may change without warning.

name: Publish Docker image

on:
  release:
    types: [published]

jobs:
  push_to_registry:
    name: Push Docker image to Docker Hub
    runs-on: ubuntu-latest
    steps:
      - name: Check out the repo
        uses: actions/checkout@v4
      
      - name: Log in to Docker Hub
        uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      
      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
        with:
          images: my-docker-hub-namespace/my-docker-hub-repository
      
      - name: Build and push Docker image
        uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
### Simple Version Workflow

This is a workflow I wrote myself, which is a bit simpler than the examples provided by GitHub.

```yaml
name: Docker Image CI
run-name: ${{ github.actor }} is building a image

on: [push]

jobs:

  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - name: Build the Docker image
      run: docker build . --file ./next-antd/AIHub.Blazor/Dockerfile --tag ${{ secrets.ALIYUN_DOCKER_REPO }}:${{ github.run_id }}
    - name: Login to aliyun docker repository
      run: docker login -u ${{ secrets.ALIYUN_USERNAME }} -p ${{ secrets.ALIYUN_PWD }} ${{ secrets.ALIYUN_DOCKER_REPO }}
    - name: Push image to aliyun docker repository
      run: docker push ${{ secrets.ALIYUN_DOCKER_REPO }}:${{ github.run_id }}

This build step constructs a Docker image on a GitHub-hosted runner and pushes it to a private repository (私有镜像仓库).

For the image's tag version, I used the run_id environment variable (环境变量), which represents the unique ID of this build task, for example, 1658821493.

Alternatively, you could use ${{ github.sha }} to represent the commit SHA of this trigger, but I chose not to as it's too long.

GITHUB_SHA: The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see "Events that trigger workflows". For example, ffac537e6cbbf934b08745a378932722df287a53.

P.S.: The official example uses a $(date +%s) timestamp, but I haven't figured out how to concatenate strings within an environment variable yet.

Setting up a Local Runner

The image is built, but it still needs to run on a local server.

It's simple—you just pull down the image and then run docker compose up. Now let's add this step to the workflow as well.

Adding a Runner

Visit this page: https://github.com/star-plan/aihub/settings/actions/runners

Click the New self-hosted runner button.

Select the corresponding system and architecture, then follow the provided commands, and you're all set.

In this guide, we've chosen the Linux - x64 approach.

The commands provided by GitHub are as follows (Note: this is just an example; the actual commands should be based on your project's configuration page).

# Create a folder
$ mkdir actions-runner && cd actions-runner# Download the latest runner package
$ curl -o actions-runner-linux-x64-2.311.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz# Optional: Validate the hash

The download step might encounter issues because, for some inexplicable reason, GitHub may be inaccessible. In such cases, you need to use the proxychains tool—please search for and configure it yourself.

PS: Wow, this runner is 180MB.

Then run the runner command

./config.sh --url [REPO_URL] --token [TOKEN]
  • You'll be prompted to enter a runner group name; the default is fine.
  • You'll need to provide a runner name; I entered aihub.
  • You can also input labels, etc., as needed.

Once the configuration is complete, start the runner.

./run.sh

Installing as a Service

The runner started with ./run.sh is unstable; it stops running after you log out. Even if you run it in the background using & or Ctrl+Z, it’s still not very reliable.

GitHub Runner can be run as a Linux service, which is also simple to use.

First, stop the currently running runner.

ps aux | grep run.sh

After finding the pid, kill it

kill -9 pid


Install service

```bash
sudo ./svc.sh install

Start service

sudo ./svc.sh start

For more operations, please refer to the fourth reference material.

View Runner List

View the currently running runners on the GitHub page

Address: https://github.com/star-plan/aihub/settings/actions/runners

You can see there is already a runner named aihub

Runner Network Proxy

GitHub is so thoughtful about this! They've already considered situations where GitHub might be inaccessible. I'm literally crying here 😭

You can set a proxy in environment variables or configure it in the .env file. I'll choose the .env method here.

https_proxy=http://proxy.local:8080
no_proxy=example.com,myserver.local:443

For more documentation on setting up a local runner, please refer to the third item in the references.

Deploying Services Using a Local Runner

As usual, we use docker-compose to orchestrate the services.

First, create a directory to store the docker-compose.yml file and some persistent files.

The path for this example is: /home/apps/aihub

version: '3.6'

services:
  web:
    image: ${IMAGE}
    container_name: aihub-test
    restart: always
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
      - ASPNETCORE_URLS=http://+:80
    volumes:
      - ./aihub.app.db:/app/aihub.app.db
    ports:
      - "12002:80"
    networks:
      - default
      - swag

networks:
  swag:
    name: swag
    external: true
  default:
    name: aihub-test

I used an environment variable for the image part because the tag of the built image is different each time. I need to write the tag into the .env file within the workflow. Of course, you could also hardcode it as latest.

The .env file looks like this:

IMAGE=registry.cn-hangzhou.aliyuncs.com/deali/aihub-test:6861065827

OK, we're finally at the last step. Let's continue editing our workflow to add a deployment job.

name: Docker Image CI
run-name: ${{ github.actor }} is building a image

on: [push]

jobs:

  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Build the Docker image
      run: docker build . --file ./next-antd/AIHub.Blazor/Dockerfile --tag ${{ secrets.ALIYUN_DOCKER_REPO }}:${{ github.run_id }}
    - name: Login to aliyun docker repository
      run: docker login -u ${{ secrets.ALIYUN_USERNAME }} -p ${{ secrets.ALIYUN_PWD }} ${{ secrets.ALIYUN_DOCKER_REPO }}
    - name: Push image to aliyun docker repository
      run: docker push ${{ secrets.ALIYUN_DOCKER_REPO }}:${{ github.run_id }}

	deployment:
    runs-on: [self-hosted, linux, x64]
    needs: [build]
    steps:
    - name: export environment variant
      run: echo 'IMAGE=${{ secrets.ALIYUN_DOCKER_REPO }}:${{ github.run_id }}' > /home/apps/aihub/.env
    - name: docker compose deploy
      run: docker compose -f /home/apps/aihub/docker-compose.yml --project-directory /home/apps/aihub up -d

Note:

  • Jobs run in parallel by default. Therefore, you need to add the needs configuration in the deployment job to ensure it runs only after the build job completes.

    Using jobs.<job_id>.needs identifies all jobs that must successfully complete before the current job runs. It can be a string or an array of strings. If any job fails or is skipped, all jobs that depend on it are skipped, unless those jobs use a conditional expression allowing it to continue. If a run contains a series of jobs that depend on each other, a failure or skip applies to all jobs in the dependency chain starting from the point of failure or skip. If you want a job to run even if its dependency jobs were not successful, use the always() conditional expression in jobs.<job_id>.if.

  • The deployment job runs locally, so runs-on uses multiple labels to select the local runner.

  • When executing the docker compose command, the working directory is inside the GitHub Action tool's _work directory. Therefore, you need to specify the --project-directory parameter or specify the .env file location using --env-file.

View Execution Results

You can view the execution results on the GitHub Action page.

Done!

References

_EOF_

Continuous Integration Guide: Automated Build and Deployment of AspNetCore Projects Using GitHub Action

本文作者:
程序设计实验室
版权声明:
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

相关文章

讨论

共 0 条

良言一句三冬暖,恶语伤人六月寒。

加载评论中…