跳至主要內容
gitea与woodpecker的cicd环境

gitea与woodpecker的cicd环境

Mr.Lexon大约 11 分钟podman

gitea与woodpecker的cicd环境

之前使用gitea与Jenkins构建出来一个ci/cd的环境,但是过程就像老太太的裹脚布一样又臭又长,而且Jenkins对于容器化的支持其实并不好,走了很多弯路,而 GitLab 占用的资源过多,不适合我的个人场景。换成 Podman + Woodpecker 之后,不仅资源占用小,也能专注项目本身,这就是我想要的简洁、高效的 CI/CD 环境。于是有了这一篇博客。

环境配置

提示

开发环境: debian 12 podman 4.3.1 podman-composer 1.0.3 rustc 1.87.0

所用容器镜像: quay.io/podman/stable latest
docker.gitea.com/gitea 1.24.1-rootless docker.io/library/debian stable-slim
docker.io/library/debian bookworm-slim
docker.io/woodpeckerci/woodpecker-server v3
docker.io/woodpeckerci/woodpecker-agent v3
docker.io/woodpeckerci/plugin-git 2.6.5
docker.io/library/rust 1.87.0

部署环境: debian bookworm-slim podman 4.3.1

注意:这里的容器镜像可直接通过此命令进行获取,后面的版本均是tag

podman pull <name>:<tag>

这里就不列出debian的安装方法了,需要的读者自行搜索如何安装debian(其实只要debian系的几乎都可以应用这篇文章)。

环境安装

首先需要安装podman:

apt install podman
podman -v

弹出版本号即为成功,podman安装的时候会提醒你需要打开KVM,这个前提是你装在物理机上面,如果安装在虚拟机上面就完全不用,可以直接使用podman就可以了。 安装podman-compose:

apt install podman-compose
podman-compose -v

弹出版本号即为成功。 安装rust:rust官网安装open in new window

ci准备

首先我们需要准备一个示例项目供ci/cd运行,以下是示例项目的目录结构:

├── Cargo.lock
├── Cargo.toml
├── Dockerfile
├── .git
├── .gitignore
├── README.md
├── src
│   └── main.rs
├── .woodpecker
│   └── main-branch.yaml
└── .woodpecker.yaml

首先我们来说明一下这个目录较为重要的文件:

  1. Dockerfile这个是用生产镜像构建。
  2. .woodpecker目录以及.woodpecker.yaml是项目的ci/cd构建配置文件 其他的文件通过cargo均会创建,接下来是创建项目的步骤。

创建Rust项目

这个的前提是完成了Rust的环境安装。 运行以下命令创建项目与进入对应项目的文件夹。

cargo new ci-cd-test
cd ci-cd-test

以下所有的配置都在ci-cd-test中进行。 依赖配置(Cargo.toml):

[package]
name = "ci-cd-test"
version = "0.1.0"
edition = "2024"

[dependencies]
actix-web = "4.11.0"

将配置复制进创建好的Cargo.toml然后运行:

cargo check

接着就是示例程序的内容:

use actix_web::{get, App, HttpResponse, HttpServer, Responder};

#[get("/")]
async fn hello() -> impl Responder {
    HttpResponse::Ok().body("Hello from Actix!")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    println!("Server starting at http://0.0.0.0:8080");
    HttpServer::new(|| {
        App::new().service(hello)
    })
    .bind(("0.0.0.0", 8080))? 
    .run()                       
    .await                    
}
#[cfg(test)]
mod tests {
    use super::*;
    use actix_web::{test,http, body::to_bytes, App};

    #[actix_web::test]
    async fn test_hello_route() {

        let app = test::init_service(App::new().service(hello)).await;

        let req = test::TestRequest::get().uri("/").to_request();

        let resp = test::call_service(&app, req).await;
        assert_eq!(resp.status(), http::StatusCode::OK);

        let body_bytes = to_bytes(resp.into_body()).await.unwrap();
        assert_eq!(body_bytes, "Hello from Actix!");
    }
}

将示例程序复制进src/main.rs,然后运行:

cargo check
cargo test

没有问题我们接着下一步。

配置ci/cd文件

创建目录结构

mkdir .woodpecker
touch .woodpecker/main-branch.yaml
touch .woodpecker.yaml

配置文件.woodpecker.yaml

pipeline: .woodpecker/

配置文件.woodpecker/main-branch.yaml:

when:
  - event: [push, pull_request]
    branch:
      - main

steps:
  - name: test
    image: docker.io/library/rust:1.87.0
    commands:
      - echo testing
      - cargo test

  - name: build
    image: docker.io/library/rust:1.87.0
    depends_on:
      - test
    commands:
      - echo building
      - cargo build --release
      - ls -lh target/release/ci-cd-test

  - name: containize
    image: quay.io/podman/stable
    depends_on:
      - build
    environment:
      CONTAINER_HOST: tcp://<你的局域网IP>:3333
    commands:
      - echo containize
      - podman --remote rmi ci-cd-test:latest || true
      - podman --remote build -t ci-cd-test:latest .

  - name: deploy
    image: quay.io/podman/stable
    depends_on:
      - containize
    environment:
      CONTAINER_HOST: tcp://<你的局域网IP>:3333
    commands:
      - echo deloying
      - podman --remote stop cicd || true
      - podman --remote rm -f cicd || true
      - podman --remote run -d --name cicd -p 8080:8080 ci-cd-test:latest

这里需要解释几个点:

  1. when结构:
when:
  - event: [push, pull_request]
    branch:
      - main

这里指的是pipeline的触发只能是push,pull_request事件,目标分支是main。 2. steps结构:

steps:
  - name: test
    image: docker.io/library/rust:1.87.0
    commands:
      - echo testing
      - cargo test

这里是定义流水线步骤的,以下是模板:

steps:
  - name: <步骤名称>
    image: <镜像名称>
    commands:
      - <需要执行的命令>

这里的镜像名称是每个步骤用的容器都是隔离的,因此镜像可以随着步骤的变化而变化(但是产物是共享的,后面会提到),并且绑定的是你宿主机的podman所以用的和下载的镜像都是宿主机的(后面会提到)。 你会看见这里有一个podman --remote这个主要用于连接宿主机的podman,因为在build阶段和deploy阶段都要使用podman并且指向都是宿主机,而且这是一个podman-in-podman环境,所以无法直接使用sock进行通信(有可行的方法但是不安全)。 3. 整体步骤解析: 1. 首先运行测试 2. 其次运行构建命令 3. 得到构建产物之后,运行生产镜像命令 4. 通过生产镜像对容器进行部署

配置运行镜像文件Dockerfile

创建Dockerfile:

touch Dockerfile

文件内容:

FROM debian:bookworm-slim

# 复制构建好的二进制文件
COPY target/release/ci-cd-test /usr/local/bin/ci-cd-test

# 公开服务端口,比如 8080
EXPOSE 8080

# 启动二进制
CMD ["ci-cd-test"]

将文件内容复制粘贴到Dockerfile里面

至此示例项目创建完成。接下来我们进行镜像下载。

镜像下载

运行以下命令进行镜像下载,podman配置的时候拒绝推断镜像来源,需要显性标注,如:docker.io/woodpeckerci/woodpecker-server:v3(这里就表明了要从docker.io上面安装)

podman pull quay.io/podman/stable:latest
podman pull docker.gitea.com/gitea:1.24.1-rootless
podman pull docker.io/library/debian:stable-slim
podman pull docker.io/library/debian:bookworm-slim
podman pull docker.io/woodpeckerci/woodpecker-server:v3
podman pull docker.io/woodpeckerci/woodpecker-agent:v3
podman pull docker.io/woodpeckerci/plugin-git:2.6.5
podman pull docker.io/library/rust:1.87.0

镜像较大和有些镜像本身有点大,所以需要耐心等待,如果受制于网络原因无法在docker.io下载,可以通过镜像站下载(需要改变docker.io或者是docker.io/library)。

整体环境配置以及部署

首先去到你想要管理的目录,然后创建giteawoodpecker的容器配置文件夹

mkdir gitea
mkdir woodpecker

gitea配置

进入gitea文件夹,创建docker-compose.yml文件,configdata文件夹:

cd gitea
mkdir config
mkdir data
touch docker-compose.yml

docker-compose.yml内容如下:

version: "3"

services:
  server:
    image: docker.gitea.com/gitea:1.24.1-rootless
    restart: always
    volumes:
      - ./data:/var/lib/gitea:U
      - ./config:/etc/gitea:U
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "2222:2222"

这里需要解释一下:U,因为我们运行的镜像是一个rootless镜像,所以需要用户权限同步。那么就需要使用:U去注明。它会自动且递归地将挂载点内所有文件的所有权(UID 和 GID)更改为容器内当前运行用户的 UID 和 GID。 将这个内容复制进docker-compose.yml然后运行:

podman-compose up

这里使用了非后台运行,以便检查错误(运行前请注意端口是否给占用)。运行之后,访问:<你的ip>:3000查看网页是否呈现: 这里可以直接配置你需要选定的数据库,这里为了方便我们来选择SQLite3,然后点击立即安装,稍等片刻之后,会看到这个页面:

点击这个注册账号,并填上信息,并点击注册账号: (目前我尚未验证这个邮箱是否有效,所以写了一个假邮箱) 注册完成之后会进入到这个界面: 然后我们来创建一个组织,并将其命名为ci-test 接着点击创建组织,然后会看到这个界面: 然后我们点击仓库列表的+号,就会进入仓库创建界面,然后我们将仓库名称配置成rust-ci-cd-test,然后点击创建仓库: 创建完成会进入到这个界面: 接着我们点击右上角的头像,头像会弹出一个下拉菜单,单击设置: 然后点击应用,下拉到底看到创建新的 OAuth2 应用程序这一栏: 内容如下,这个http://192.168.2.142:8000/authorize里面的192.168.2.142需要更换成你的ip 然后点击创建应用,创建成功之后会跳转到这个页面: 此时需要把红框里面的值都要复制下来,后面有用。目前为止,gitea配置已经完成了。 完成之后我们需要将gitea结束掉,然后让他在后台运行:

podman-compose up -d

woodpecker配置

进入woodpecker文件夹,创建docker-compose.yml文件,woodpecker_configwoodpecker_data文件夹:

cd woodpecker
mkdir woodpecker_config
mkdir woodpecker_data
touch docker-compose.yml

然后我们还需要生成一串加密密钥:

openssl rand -hex 32

他长这样:

7361384a1f1f2b7cbc0c58de259ca76a385e21f9ee27b074d723102d7fba1a78

需要把类似的这一串字符放到WOODPECKER_AGENT_SECRET这里面来 接着我们还要获取宿主机的podman sock:

podman info --format '{{.Host.RemoteSocket.Path}}'

获取到的结果:

/run/user/1000/podman/podman.sock

docker-compose.yml的文件内容:

services:
  woodpecker-server:
    image: docker.io/woodpeckerci/woodpecker-server:v3
    ports:
      - 8000:8000
    volumes:
      - woodpecker-server-data:/var/lib/woodpecker/
    environment:
      - WOODPECKER_OPEN=true
      - WOODPECKER_HOST=http://<你的ip>:8000
      - WOODPECKER_GITEA=true
      - WOODPECKER_GITEA_URL=http://<你的ip>:3000
      - WOODPECKER_GITEA_CLIENT=<上面复制的客户端ID>
      - WOODPECKER_GITEA_SECRET=<上面复制的客户端SECRET>
      - WOODPECKER_AGENT_SECRET=<刚刚通过ssl生成的加密密钥>

  woodpecker-agent:
    image: docker.io/woodpeckerci/woodpecker-agent:v3
    command: agent
    restart: always
    depends_on:
      - woodpecker-server
    volumes:
      - woodpecker-agent-config:/etc/woodpecker
      - <通过命令行获取到的podman sock>:/var/run/docker.sock:U
    environment:
      - WOODPECKER_SERVER=woodpecker-server:9000
      - WOODPECKER_AGENT_SECRET=<刚刚通过ssl生成的加密密钥>

volumes:
  woodpecker-server-data:
    driver: local
    driver_opts:
      type: 'bind'
      o: 'bind'
      device: './woodpecker_data'
  woodpecker-agent-config:
    driver: local
    driver_opts:
      type: 'bind'
      o: 'bind'
      device: './woodpecker_config'

将上面需要替换的字段替换完成之后,把他复制进docker-compose.yml 然后运行:

podman-compose up

运行之后,访问<你的ip>:8000,你会看到一个: 然后单击前往授权,然后会跳转到这个页面: 然后点击应用授权,之后你就会进入后台: 接着我们点击添加仓库,然后点击启用: 点击启用之后,你会看到: 至此,woodpecker的配置就完成了。

仓库配置

我们首先要去gitea配置文件中添加这一条(路径就是/<你存放gitea的路径>/gitea/config/app.ini):

[webhook]
ALLOWED_HOST_LIST = <你的ip>, <你的ip>:8000

其次我们要生成ssh密钥:

ssh-keygen -t ed25519 -C "admin@example.com"

生成出来的结果:

Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/lexon/.ssh/id_ed25519): /home/lexon/.ssh/cicdtest #这里是写你期望将密钥存放的地方
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/lexon/.ssh/cicdtest
Your public key has been saved in /home/lexon/.ssh/cicdtest.pub
The key fingerprint is:
SHA256:KwA2jcAUWrX8kNdQ9nYQ1/okh23r69JcCm7hE5Rb1SA admin@example.com
The key's randomart image is:
+--[ED25519 256]--+
|+oo.. ..o o.E....|
|.+ + o + . o .. o|
|. = * . . o .= . |
| . o +   . .* *  |
|    . . S  . O . |
|     .   .  = o .|
|      . .  o B o |
|       .    * =  |
|           . +o. |
+----[SHA256]-----+

然后将/<你的密钥地址>/cicdtest.pub里面的内容打印出来,然后登录gitea,点击头像,然后点击设置: 然后我们找到: 点击增加密钥,我们会看到这个界面,将公钥和名称填写上去,然后点击增加密钥: 完成之后,我们给本地的ssh添加一个alias,文件路径~/.ssh/config,没有的话直接创建一个,内容如下:

Host gitea
    HostName <你的ip>
    User git
    Port 2222
    IdentityFile <密钥路径>

将内容替换之后写入文件。 之后,我们再去到示例代码的文件目录:

cd ci-cd-test

提交文件内容:

git add .
git commit -m "ci:test"
git branch -m main

添加远程库:

git remote set-url origin gitea:/ci-test/rust-ci-cd-test.git

至此,仓库正式配置完成。

ci/cd测试

先开启podman远程登录:

podman system service --time=0 tcp:0.0.0.0:3333 &

然后接着上一步完成之后直接推送:

git push origin main

之后登录<你的ip>:8000看到流水线运行成功了就证明流程正式完成了。 这个状态就是正在运行流水线,因为第一次运行需要获取资源,所以比较慢: 这个状态就表示构建完成了: 接着可以在podman看到构建好的镜像: 服务容器也在运作: 服务都可访问: 至此,整一套单机部署的ci/cd流程正式结束。

总结与展望

总的来说这一套方案比起Jenkins来说要好不少,而且比起gitlab消耗的资源更低,不过在部署中还是发现一些现象:

  1. 在流水线构建过程中貌似不能使用podman
  2. 为了演示,将构建服务器和部署服务器二合一了,在生产来说是需要分离的。
  3. woodpecker看起来功能较为简陋,但是基本够用,本教程还没有演示如何使用插件,目前笔者还在探索中。 最后,这个方案对于一些小团队来说还是很实用的,避免付费的同时,代价至少也是比较明显,就是必须设置一台机器用于ci的,还能有效地管理容器化,未来打算探索:
  • 探索其插件系统
  • 集成容器仓库
  • 集成流量监控
  • 探索分布式极限
  • 探索分环境部署,分支部署。

补充

如果发现了: 这表明你的gitea的app.ini文件的Webhook配置有问题,所以需要检查一下

上次编辑于:
贡献者: Lexon