代码托管
已开启的高级功能
Gravatar
Reply by email
Advanced Search
Container Registry
Shared Runners
- Linux Docker
Git镜像教程
NJU Git可以设置从其他代码托管平台如GitHub、GitLab、Gitee等拉取、推送代码仓库(镜像)。
教程见:https://mp.weixin.qq.com/s/EjAedt6A3PvuASGlFCfyWQ
CI/CD
如何使用CI/CD功能在git.nju.edu.cn实现高级的自动化开发工作流
CI/CD 使用指南
先选方式
| 需求 | 方式 | runner tag |
|---|---|---|
| 编译 / 构建 / 打包 / 测试 / 部署等日常作业 | 常规 job | 不用写(公共 runner) |
| 只构建并推送镜像 | kaniko | 不用写(公共 runner) |
docker run / compose 等 Docker in Docker |
DinD | dind(公共 runner) |
- 用公共(共享)runner:不写
tags(常规 job、kaniko 就是这种;也可显式写tags: [docker])。 - 用公共 DinD:必须写
tags: [dind]——只有带这个 tag 的 runner 提供 job 内 Docker。 - 用你自己的 runner(自建本地 runner):写你注册时设的 tag,例如
tags: [myrunner]。 - 注意:项目里有自己的 runner 且开了 "Run untagged jobs" 时,不写 tags 的 job 会优先被它接走(通常正是你想要的)。
常规 job
编译、构建产物、打包、测试、代码检查、部署……都属于「常规 job」:在 job 容器里跑命令、不构建镜像。它们的差别只是 stage 和 script 里跑什么;套路是 一个 job = 一个镜像 + 一串命令 +(可选)产物。
- 特点:最快、最省资源、隔离最简单(没有 Docker daemon)。
- 不能
docker build;要产出镜像请看下面 kaniko / DinD 两节。 - 任意公共镜像都可用(Docker Hub / gcr.io / ghcr.io / quay.io / nvcr.io / registry.gitlab.com / registry.k8s.io )。
编译 / 构建产物
build:
stage: build
image: node:lts
script:
- npm ci
- npm run build
artifacts:
paths: [dist/]
测试
test:
stage: test
image: node:lts
needs: [build]
script:
- npm ci
- npm test -- --reporter=junit --outputFile=report.xml
artifacts:
when: always
reports:
junit: report.xml
代码检查 / 静态分析
lint:
stage: test
image: node:lts
script:
- npm ci
- npx eslint .
打包归档
package:
stage: package
image: alpine:latest
needs: [build]
script:
- tar -czf myapp.tar.gz -C dist .
artifacts:
paths: [myapp.tar.gz]
用到
package这类非默认 stage 时,要在文件顶部声明:stages: [build, test, package, deploy](GitLab 默认只有.pre / build / test / deploy / .post)。
部署(deployment job)
部署方式很多(rsync、scp、sftp、WebDAV、对象存储、kubectl、调用发布接口……),这里只给一个最常见的例子,换传输方式只需改 script,凭据的处理方式不变。
核心原则:敏感信息(私钥、密码、Token)只放 GitLab 的 CI/CD 变量里,绝不写进仓库、.gitlab-ci.yml 或提交的文件。
deploy:
stage: deploy
image: alpine:latest
variables:
GIT_STRATEGY: none
environment:
name: production
url: https://myapp.example.com
resource_group: production
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
before_script:
- apk add --no-cache openssh-client rsync
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- install -m 600 "$SSH_PRIVATE_KEY" ~/.ssh/id_ed25519
- install -m 644 "$SSH_KNOWN_HOSTS" ~/.ssh/known_hosts
script:
- rsync -az --delete -e "ssh -i ~/.ssh/id_ed25519" dist/ deploy@my-server:/srv/myapp/
- ssh -i ~/.ssh/id_ed25519 deploy@my-server 'cd /srv/myapp && docker compose up -d'
敏感信息存哪里(Settings → CI/CD → Variables):
| 变量 | 类型 | 说明 |
|---|---|---|
SSH_PRIVATE_KEY |
File | 部署专用私钥(单独生成,不要用个人密钥)。File 类型下 $SSH_PRIVATE_KEY 是临时文件路径,用 install -m 600 "$SSH_PRIVATE_KEY" ~/.ssh/id_ed25519 复制,私钥内容不会出现在日志里 |
SSH_KNOWN_HOSTS |
Variable 或 File | 目标机公钥指纹(ssh-keyscan my-server);不要用 StrictHostKeyChecking=no |
| 密码 / Token | Variable | 勾 Masked;生产再勾 Protected(只在受保护分支的 job 里可见) |
要点:
- 私钥只存在于 job 的临时容器里(权限 600),job 结束随容器销毁;仓库里永远没有它。
- 目标机不要开密码登录,使用最小权限的专用部署账号。
- 生产发布建议限定 protected branch +
when: manual+environment+resource_group。 - 拉私有仓库代码用 deploy token / deploy key,不要用个人 PAT。
kaniko
在 job 里构建镜像并推送到 registry,不需要 Docker daemon。
- 只能构建并推送镜像,不能
docker run/compose;不支持RUN --mount=type=secret;不支持跨架构构建。 - 不需要特权、不需要 DinD、任意 runner 都能跑。
- 缓存:
--cache-repo指向本项目 registry。 - 关于 tag:
debug是浮动 tag(自动跟随上游最新版),不要用latest(那是 scratch 镜像,没有 shell,CI 脚本根本跑不起来)。 - 上游
GoogleContainerTools/kaniko已归档(2025-06,冻结在 v1.24.0);martizih/kaniko属于仍活跃的替代分支osscontainertools/kaniko。
build-image:
stage: build
image:
name: martizih/kaniko:debug # 浮动 tag,始终带 shell;等价 ghcr.io/osscontainertools/kaniko:debug
entrypoint: [""]
script:
- mkdir -p /kaniko/.docker
- |
cat > /kaniko/.docker/config.json <<CONF
{"auths":{"${CI_REGISTRY}":{"username":"${CI_REGISTRY_USER}","password":"${CI_REGISTRY_PASSWORD}"}}}
CONF
- /kaniko/executor
--context "${CI_PROJECT_DIR}"
--dockerfile "${CI_PROJECT_DIR}/Dockerfile"
--destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}"
--destination "${CI_REGISTRY_IMAGE}:latest"
--snapshot-mode=redo
--compressed-caching=false
--cache=true
--cache-repo="${CI_REGISTRY_IMAGE}/cache"
DinD
在 job 里用完整 Docker(build / run / compose)
- 无需
privileged(runner 侧使用隔离运行时,容器是真正的 user-namespace 隔离)。 - 支持
docker build/run/compose,以及同架构的docker buildx;隔离运行时里binfmt_misc不可用,跨架构(multi-arch / QEMU 模拟)构建做不了;需要多架构镜像请使用自己的构建机。 - 内层 daemon 每个 job 都是全新的,镜像层不跨 job 保留;要复用请用 registry 缓存/artifacts,不要共享 docker 数据目录。
build-and-test:
stage: test
tags: [dind] # ← 必须,否则会被派到没有 Docker daemon 的 runner
image: docker:29-cli
services:
- name: docker:dind
alias: docker
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: "" # 明文 2375,仅 job 内部网络
script:
- docker build -t myapp:ci .
- docker run --rm myapp:ci ./run-tests.sh
- docker compose up -d --build && docker compose ps
自建本地 runner
什么时候用:不方便用共享 runner(需要特殊硬件、本地数据、自定义镜像环境),或想把构建放在自己的机器上。
在 GitLab 上创建 runner
- 位置二选一:项目 → Settings → CI/CD → Runners → New project runner。
- 勾选 Tags,例如
myrunner—— 这是 job 匹配 runner 的唯一依据;不建议勾 "Run untagged jobs"(会跟共享 runner 抢任务)。 - 创建后复制 runner authentication token(
glrt-...,只显示一次,等同凭据,不要提交进仓库)。
在目标机器上安装注册 runner
安装与注册方式很多(Docker、deb/rpm 包、二进制、Kubernetes 等),请按你的环境选择,参考 GitLab 官方文档:
注册时 --url 填 https://git.nju.edu.cn/,--token 填上一步的 glrt-...,--executor 按需。
使用与验证
命名与凭据
- 推送目标统一用
${CI_REGISTRY_IMAGE}(=reg.nju.edu.cn/<namespace>/<project>),不要硬编码仓库地址。 - 拉取本项目/组内私有镜像用
$CI_JOB_TOKEN;外部 registry 凭据放 Masked + Protected 的 CI 变量。 - 凭据不要写进
.gitlab-ci.yml,也不要--build-arg SECRET=...烧进镜像层(dind 可用RUN --mount=type=secret,kaniko 不支持)。 - Docker Hub 上游图片可走 Dependency Proxy(需要改镜像名)。
常见问题
自动化构建Docker镜像实践
一个通过CI/CD自动构建Docker镜像并用于HPC集群计算的简单示例。
示例代码仓库:https://git.nju.edu.cn/escience/singularity-example
容器化在集群上使用时有很大的优势,主要体现在:
- 容器内模拟的root权限,无需权限即可任意安装自己想要的程序包
- 版本可控,自定义性强
- 可复用性强,一处构建,多处部署
因此,对于一些编译特别复杂但早已有Docker镜像的计算软件,或者自己写的配置环境麻烦的软件,考虑Singularity是个不错的选择。
此外,eScience中心的多个服务也涵盖了一整套工作流。所以全程无需出校访问在校园内网即可实现。
本文将通过一个简单但常见的Conda虚拟环境下Python Numpy库的镜像构建来演示如何使用这一功能。
通过 Dockerfile 构建自己的镜像
通过Dockerfile,可以事先构建一个Docker镜像。
FROM continuumio/miniconda3:22.11.1
# 使用南大镜像站conda源
COPY .condarc /root/.condarc
# 创建环境
RUN conda create -n my-env python=3.10 numpy
# 激活环境
SHELL ["/bin/bash", "--login", "-c"]
RUN conda init bash
RUN echo "source activate my-env" > ~/.bashrc
ENV PATH /opt/conda/envs/my-env/bin:$PATH
此处.condarc是修改为了南京大学镜像站的软件源以保证速度。如果要安装其他的包,把numpy改成你要的包即可,同时还可以控制Python版本。
如若本地有Docker,可通过docker本地尝试构建:
docker build -t escience/conda-numpy .
Docker也可以使用Docker缓存
docker.nju.edu.cn。
构建完毕后,本地跑一下相关的脚本以测试:
docker run -i escience/conda-numpy python < test.py
设置CI/CD
CI/CD是代码托管服务git.nju.edu.cn的一个自动化工具,在代码仓库下的.gitlab-ci.yml文件中配置CI/CD,可以让服务器按照设定执行自动构建、编译、测试、集成、部署等等。
CI/CD需要一个基础镜像来运行,这个镜像gcr.nju.edu.cn也有缓存服务。文件内容如下:
# 执行顺序
stages:
- build
- test
# 自动构建镜像
build:
stage: build
image:
name: martizih/kaniko:debug
entrypoint: [""]
script:
- /kaniko/executor
--context "${CI_PROJECT_DIR}"
--dockerfile "${CI_PROJECT_DIR}/Dockerfile"
--destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG}"
rules: # 只在推送标签时触发
- if: $CI_COMMIT_TAG
# 使用文件来测试
test:
stage: test
image:
name: ${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG}
script:
- python "${CI_PROJECT_DIR}/test.py"
rules:
- if: $CI_COMMIT_TAG
推送代码到仓库后,为某个提交创建标签,例如此处我们创建为test,这也会成为镜像的标签。
触发流水线任务后,如果通过,在 “CI/CD”-“流水线” 可以看到作业情况:
在自动构建成功后,即可在 “软件包与镜像库”-“容器镜像库” 中查看到此镜像。
当然,这里你也不一定必须用Kaniko的构建方案,此处是为了实现自动化。你也可以本地构建完毕后通过
docker push命令直接推送至reg.nju.edu.cn来托管。此外,Singularity有其自己的定义文件(类似Dockerfile),可以用类似的方式一步到位而不需要单独构建一个Docker镜像。
在集群上使用
hpc.nju.edu.cn唐楼集群的简单基本使用见《校内用户超算集群申请与基本使用简明指南》一文。
登录集群通过此镜像来构建Singularity镜像:
singularity build conda-numpy.sif docker://reg.nju.edu.cn/escience/singularity-example:test
虽然镜像较大,但是可以看见速度仍然很快,这正是开头全程校园内网带来的好处。
由于示例是一个numpy的环境,后续我们对应地使用作业脚本即可。创建一个job.lsf文件:
#BSUB -q 6140ib
#BSUB -n 1
module load singularity/latest
SINGULARITY="singularity run --env MKL_NUM_THREADS=$LSB_DJOB_NUMPROC conda-numpy.sif"
${SINGULARITY} python test.py
然后执行
bsub < job.lsf
通过bjobs查看任务运行情况,完成后通过bpeek指令可以查看输出,可以看到,任务成功提交并正确执行了。
总结
通过CI/CD自动构建Docker镜像并用于HPC集群计算的简单示例,我们尝试将eScience的下列服务进行了结合:
- 缓存/镜像加速:
docker.nju.edu.cn、gcr.nju.edu.cn、mirror.nju.edu.cn - CI/CD自动构建:
git.nju.edu.cn、reg.nju.edu.cn - 计算集群:
hpc.nju.edu.cn
在这个过程中,这些服务的灵活使用有助于从版本控制、环境搭建、迁移部署等诸多方面建立可靠的工作流程,最终得以节省时间、便捷开发,提升创新协同的质量。