返回文章列表文章索引阅读全文
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 许可协议。转载请注明出处!

相关文章

GitLab的安装、配置、使用

前言 上周去参与“中国数字经济创新发展大会”了,然后又忙新项目的事情,博客又有一段时间没有更新,今天周一事情比较少,立刻开始写文,最近有挺多值得记录的东西~ 进入正文,最近我们搭了个Gitlab,并且把正在开发的项目代码都迁移到自建的Gitlab上,整个过程还是比较流畅的~~(就是有点费内存~~ PS:立即下单给这台服务器买了四条16G内存组四通道~ 在此之前,我们团队一直使用Gitee企业版作为

2022-07-21 · DevOps · 约 1 分钟

GitLab的CI_CD配置与使用

前言 写代码这项工作,本质就是将工作自动化,减少手工操作提供效率,~~因为人的本质都是懒狗~~,程序员也不能例外,为了各种意义的效率提升~~(懒)~~,我们需要持续集成工具,将代码测试、编译、发布这些重复性很高的工作,全部变成自动化,减轻工作量,提高效率,解放生产力,~~为华夏伟大复兴贡献力量~~ 说回正题,持续集成工具主流的就 Jenkins 、Gitlab CI/CD 这些,因为我们刚好在用

2022-09-30 · DevOps · 约 1 分钟

讨论

共 0 条

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

加载评论中…