1 基本介绍
CODING 是一个面向软件研发团队的研发协作管理平台,提供云原生开发环境、在线编码、代码托管、项目管理、测试管理、持续集成、制品库、持续部署、团队知识库等系列工具产品,帮助研发团队实践敏捷开发与 DevOps。
2 核心特性
本文仅讨论 CODING 提供的持续集成、制品库、持续部署功能。
2.1 持续集成
2.1.1 构建计划
首先,创建您的项目,本文以“基础平台”为例。从项目进入“持续集成”菜单,选择“构建计划”,界面示例如下。
创建您的构建计划,根据实际情况选择 CODING 代码仓库或者关联外部的代码仓库(本文以腾讯云工蜂为例)。
进入下一步,你可以选择图形化编辑器或者文本编辑器完成 Jenkins 构建脚本的创建。
环境变量是一个很重要的功能,我们可以在这个基础上设置密码、Token,Profile 等逻辑信息。

笔者认为 CODING 应该在此处新增版本历史的功能,如果不小心误删这项构建计划,写了半天的 Jenkins 脚本就没了。目前的代替方案是在我们的 Git 目录添加名为 .coding 的目录,把内容保存到 Jenkinsfile 文件中,基于 Git 进行版本控制。
然后,回到配置界面的“基础信息”选项卡,修改 CODING 读取 Jenkinsfile 的位置。
接着,根据你的配合使用情况去调整触发规则(每次 commit 就触发一次并不是很好哦~)。
在“变量与缓存”设置相关环境参数和缓存目录。
有时候,我们更新依赖可能不生效,需要你手动在配置界面点击“重置缓存”再次拉取依赖。
2.1.2 自定义计划模板
CODING 提供了自定义构建计划,我们可以把之前调试好的配置保存为模板。建议把通用脚本、常用变量、缓存目录写在模板里面。
如果需要调整保存后的模板,可以从“团队设置中心”> “功能设置”>“持续集成”进去。
2.2 制品库
2.2.1 仓库管理
新建仓库(建议分别创建 Docker、Maven、npm)
为制品库设置代理(其实默认的代理就够用了)
2.2.2 构建制品
在仓库管理有个“操作指引”,按着官方的说明,分别配置制品的凭据、推送和拉取。
2.3 持续部署
2.3.1 部署到 K8s 集群
CODING 持续部署需要绑定目标集群,以腾讯云 EKS(弹性集群)为例。CODING 目前支持三种云账号类型:
- 腾讯云 TKE:如果您是从腾讯云开发者平台入口注册登录,才会显示此类账号。
- Kubernetes:支持 Kubeconfig 和 Service Account 两个常用凭据。
- 腾讯云账号:即腾讯云 API 密钥。
在创建构建计划的环节我们选择了关联代码仓库,因此 CODING 对外部代码仓库实现部署只能通过 Kubernetes 授权访问凭据的方式完成(官方给出的答复)。
首先,Kubernetes 授权访问凭据需要我们在腾讯云的 EKS 目标集群设置 CODING 通过外网访问的白名单。
设置白名单后,复制界面生成的 Kubeconfig 内容到 CODING 的 “部署控制台” >“云账号”> “绑定云账号”。 
绑定成功后,在构建计划的“持续部署”选项会出现绑定后的 K8s 选项。
如果有不清楚的地方,可以参考下官方的步骤。
2.3.2 主机部署
TODO
3 实战案例
3.1 Spring Boot 工程实战
3.1.1 关联 Git 代码仓库
以阿里的 Sentinel 为例,先关联好对应的仓库。
修改 Sentinel 的 pom.xml 文件,本文使用基于 Google 的 jib 插件打包 Docker 镜像,其他场景可以根据 Dockerfile 或者其他 Maven 插件实现同样的功能。
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.alibaba.csp</groupId><artifactId>sentinel-parent</artifactId><version>1.8.2</version></parent><artifactId>sentinel-dashboard</artifactId><packaging>jar</packaging><version>1.8.2</version><properties><!-- 此处省略一些内容,下列内容是新增的部分 --><docker.username/><docker.password/><docker.image>shjrccr.ccs.tencentyun.com/xxx/sentinel</docker.image> <!-- 对应目标集群的镜像残酷,我们这里选择腾讯云的镜像仓库 --><versions-maven-plugin.version>2.7</versions-maven-plugin.version> <!-- 增加这个插件实现动态版本打包 --></properties><build><finalName>sentinel-dashboard</finalName><plugins><plugin><groupId>org.codehaus.mojo</groupId><artifactId>versions-maven-plugin</artifactId><version>${versions-maven-plugin.version}</version><configuration><generateBackupPoms>false</generateBackupPoms></configuration></plugin></plugins></build><profiles><profile><id>coding</id><properties><spring.profiles.active>prod</spring.profiles.active></properties><build><plugins><!-- 基于 Google 的 jib 插件打包镜像 --><plugin><groupId>com.google.cloud.tools</groupId><artifactId>jib-maven-plugin</artifactId><version>2.2.0</version><configuration><from><image>openjdk:11-jre-slim</image></from><to><image>${docker.image}</image><auth><username>${docker.username}</username><password>${docker.password}</password></auth><tags><tag>${project.version}</tag></tags></to><container><entrypoint><shell>bash</shell><option>-c</option><arg>/entrypoint.sh</arg></entrypoint><ports><port>8080</port></ports><environment><TZ>Asia/Shanghai</TZ><SPRING_OUTPUT_ANSI_ENABLED>ALWAYS</SPRING_OUTPUT_ANSI_ENABLED><JAVA_SLEEP>1</JAVA_SLEEP><JAVA_OPTS>-Xms256m -Xmx256m</JAVA_OPTS></environment><creationTime>USE_CURRENT_TIMESTAMP</creationTime><mainClass>com.alibaba.csp.sentinel.dashboard.DashboardApplication</mainClass></container><extraDirectories><paths>src/main/docker/jib</paths><permissions><permission><file>/entrypoint.sh</file><mode>755</mode></permission></permissions></extraDirectories><allowInsecureRegistries>true</allowInsecureRegistries></configuration></plugin></plugins></build></profile></profiles></project>
可能有些同学注意到了,pom.xml 没有指定 Coding 的仓库地址。一般我们建议把外部环境配置放在 Maven 的 settings.xml 文件,这样做是为了减少代码的侵入。
本地开发时可以参考下面的配置。
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 http://maven.apache.org/xsd/settings-1.2.0.xsd"><localRepository>本地仓库路径</localRepository><pluginGroups></pluginGroups><proxies></proxies><servers><!-- coding --><server><id>coding</id><username>Coding 制品库用户名</username><password>Coding 制品库密码</password></server></servers><mirrors></mirrors><profiles><profile><id>coding</id><properties><!-- docker.username --><docker.username>上传 Docker 镜像仓库的用户名</docker.username><docker.password>上传 Docker 镜像仓库的密码</docker.password><altReleaseDeploymentRepository>coding::default::https://xxx-maven.pkg.coding.net/repository/xxx/maven-releases/</altReleaseDeploymentRepository><altSnapshotDeploymentRepository>coding::default::https://xxx-maven.pkg.coding.net/repository/xxx/maven-snapshots/</altSnapshotDeploymentRepository></properties><repositories><repository><id>xxx-maven-releases</id><name>xxx-maven-releases</name><url>https://xxx-maven.pkg.coding.net/repository/xxx/maven-releases/</url><releases><enabled>true</enabled></releases><snapshots><enabled>false</enabled></snapshots></repository><repository><id>xxx-maven-snapshots</id><name>xxx-maven-snapshots</name><url>https://xxx-maven.pkg.coding.net/repository/xxx/maven-snapshots/</url><releases><enabled>false</enabled></releases><snapshots><enabled>true</enabled></snapshots></repository></repositories><pluginRepositories><pluginRepository><id>xxx-maven-releases</id><name>xxx-maven-releases</name><url>https://xxx-maven.pkg.coding.net/repository/xxx/maven-releases/</url><releases><enabled>true</enabled></releases><snapshots><enabled>false</enabled></snapshots></pluginRepository><pluginRepository><id>xxx-maven-snapshots</id><name>xxx-maven-snapshots</name><url>https://xxx-maven.pkg.coding.net/repository/xxx/maven-snapshots/</url><releases><enabled>false</enabled></releases><snapshots><enabled>true</enabled></snapshots></pluginRepository></pluginRepositories></profile></profiles><activeProfiles><activeProfile>coding</activeProfile></activeProfiles>
3.1.2 初始化 Maven 配置文件
在 Coding 构建时需要手动指定 settings.xml 文件,所以,我们可以把文件放在 .coding 目录下。文件内容中有 ${env.xxx} 的变量值,通过 Coding 的变量传递。
<?xml version="1.0" encoding="UTF-8"?><settings><servers><server><id>coding</id><username>${env.MAVEN_USERNAME}</username><password>${env.MAVEN_PASSWORD}</password></server></servers><profiles><profile><id>coding</id><properties><docker.username>${env.DOCKER_USERNAME}</docker.username><docker.password>${env.DOCKER_PASSWORD}</docker.password><docker.image>${env.DOCKER_IMAGE}</docker.image></properties><repositories><repository><id>coding</id><url>${env.MAVEN_REPO_URL}</url><releases><enabled>true</enabled></releases><snapshots><enabled>true</enabled></snapshots></repository></repositories><pluginRepositories><pluginRepository><id>coding</id><url>${env.MAVEN_REPO_URL}</url><releases><enabled>true</enabled></releases><snapshots><enabled>false</enabled></snapshots></pluginRepository></pluginRepositories></profile></profiles><activeProfiles><activeProfile>coding</activeProfile></activeProfiles></settings>
3.1.2 调整 Jenkinsfile 构建脚本
进入 Coding 图形化编辑器,按“开始 -> 检出 -> 编译 -> 推送到 Maven 制品库 > 推送到 Docker 制品库”依次配置。
由于配置步骤比较多,在这里直接提供一份调试好的内容,复制下面的内容到 Coding 的“文本编辑器”中,然后切换为 “图形化编辑器”,Coding 会生成相应的可视化界面,按需调整即可。
pipeline {agent anyenvironment {CODING_MAVEN_REPO_NAME = "maven"CODING_MAVEN_REPO_ID = "${CCI_CURRENT_TEAM}-${PROJECT_NAME}-${CODING_MAVEN_REPO_NAME}"CODING_MAVEN_REPO_URL = "${CCI_CURRENT_WEB_PROTOCOL}://${CCI_CURRENT_TEAM}-maven.pkg.${CCI_CURRENT_DOMAIN}/repository/${PROJECT_NAME}/${CODING_MAVEN_REPO_NAME}/"CODING_DOCKER_REPO_NAME = "docker"CODING_DOCKER_REPO_HOST = "${CCI_CURRENT_TEAM}-docker.pkg.${CCI_CURRENT_DOMAIN}"CODING_DOCKER_REPO_URL = "${CODING_DOCKER_REPO_HOST}/${PROJECT_NAME}/${CODING_DOCKER_REPO_NAME}/${DEPOT_NAME}"TCR_NAMESPACE_NAME = "test"TCR_DOCKER_REPO_URL = "shjrccr.ccs.tencentyun.com/${TCR_NAMESPACE_NAME}/${DEPOT_NAME}"}stages {stage('检出') {steps {checkout([$class: 'GitSCM',branches: [[name: GIT_BUILD_REF]],userRemoteConfigs: [[url: GIT_REPO_URL,credentialsId: CREDENTIALS_ID]]])}}stage('编译') {steps {script {if (env.TAG_NAME ==~ /.*/ ) {CODING_ARTIFACT_VERSION = "${env.TAG_NAME}"} else if (env.MR_SOURCE_BRANCH ==~ /.*/ ) {CODING_ARTIFACT_VERSION = "mr-${env.MR_RESOURCE_ID}-${env.GIT_COMMIT_SHORT}"} else {CODING_ARTIFACT_VERSION = "${env.BRANCH_NAME.replace('/', '-')}-${env.GIT_COMMIT_SHORT}"}}withCredentials([usernamePassword(credentialsId: env.CODING_ARTIFACTS_CREDENTIALS_ID,usernameVariable: 'CODING_ARTIFACTS_USERNAME',passwordVariable: 'CODING_ARTIFACTS_PASSWORD')]) {withEnv(["MAVEN_REPO_URL=${CODING_MAVEN_REPO_URL}","MAVEN_USERNAME=${CODING_ARTIFACTS_USERNAME}","MAVEN_PASSWORD=${CODING_ARTIFACTS_PASSWORD}","CODING_ARTIFACT_VERSION=${CODING_ARTIFACT_VERSION}"]) {sh 'mvn -Pcoding versions:set -DnewVersion=${CODING_ARTIFACT_VERSION} clean package -DskipTests -s ./.coding/settings.xml'}}}}stage('推送到 Maven 制品库') {steps {withCredentials([usernamePassword(credentialsId: env.CODING_ARTIFACTS_CREDENTIALS_ID,usernameVariable: 'CODING_ARTIFACTS_USERNAME',passwordVariable: 'CODING_ARTIFACTS_PASSWORD')]) {withEnv(["MAVEN_REPO_URL=${CODING_MAVEN_REPO_URL}","MAVEN_USERNAME=${CODING_ARTIFACTS_USERNAME}","MAVEN_PASSWORD=${CODING_ARTIFACTS_PASSWORD}"]) {sh 'mvn -Pcoding deploy -DskipTests -s ./.coding/settings.xml'}}}}stage('推送到 Docker 制品库') {steps {withEnv(["DOCKER_USERNAME=${DOCKER_USERNAME}","DOCKER_PASSWORD=${DOCKER_PASSWORD}","DOCKER_IMAGE=${TCR_DOCKER_REPO_URL}:${CODING_ARTIFACT_VERSION}"]) {sh 'mvn -Pcoding jib:build -Djib.disableUpdateChecks=true -DskipTests -s ./.coding/settings.xml'}}}stage('部署到 K8s 集群') {steps {withEnv(["DOCKER_IMAGE=${TCR_DOCKER_REPO_URL}:${CODING_ARTIFACT_VERSION}"]) {cdDeploy(deployType: 'PATCH_IMAGE', application: '${CCI_CURRENT_TEAM}', pipelineName: '${PROJECT_NAME}-${CCI_JOB_NAME}-2222222', image: '${DOCKER_IMAGE}', cloudAccountName: 'guoyuanlu-k8s-test', namespace: 'test-college', manifestType: 'Deployment', manifestName: 'sentinel-dashboard', containerName: 'sentinel-dashboard', credentialId: 'xxxxxxxxxxxxxxxxxxxxxxx', personalAccessToken: '${CD_PERSONAL_ACCESS_TOKEN}')}}}}}
在“部署到 K8s 集群”的步骤中,您需要在界面手动选择集群,生成新的 Token,否则部署会报错。
选择对应的 Deployment 后保存,切回“文本编辑器”查看 credentialId 的值是否发生变化。
配置成功的效果图如下。
3.2 Vue.js 工程实战
3.2.1 关联 Git 代码仓库
以开源的若依项目(Vue)为例,先关联好对应的仓库。
在代码目录下分别创建以下目录:
- .coding/Jenkinsfile
- .coding/nginx.conf
- .npmrc
- Dockerfile
3.2.2 初始化 .npmrc 文件
将 npm 的私服指向 CODING 制品库。
registry=https://xxx-npm.pkg.coding.net/xxx/npm/always-auth=true//xxx-npm.pkg.coding.net/xxx/npm/:username=${CODING_ARTIFACTS_USERNAME}//xxx-npm.pkg.coding.net/xxx/npm/:_password=${CODING_ARTIFACTS_PASSWORD}//xxx-npm.pkg.coding.net/xxx/npm/:email=xxx@gmail.com
3.2.3 初始化 nginx.conf 文件
前端依赖 Nginx 的镜像进行部署,所以,我们要把 nginx 的配置也加上。
user nginx;worker_processes 1;error_log logs/error.log warn;pid logs/nginx.pid;events {worker_connections 1024;}http {include /etc/nginx/mime.types;default_type application/octet-stream;log_format main '$remote_addr - $remote_user [$time_local] "$request" ''$status $body_bytes_sent "$http_referer" ''"$http_user_agent" "$http_x_forwarded_for"';access_log logs/access.log main;sendfile on;keepalive_timeout 65;client_max_body_size 500m;server {listen 80;server_name localhost;charset utf-8;gzip_static on;gzip_vary on;gzip_min_length 1k;gzip_comp_level 9;gzip_types text/css text/javascript application/javascript application/x-javascript application/xml;gzip_disable "MSIE [1-6]\.";error_page 500 502 503 504 /50x.html;location = /50x.html {root /usr/share/nginx/html;}location / {root /usr/share/nginx/html;index index.html index.htm;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}location ^~ /api/ {proxy_pass http://xxx-admin-server:8070/;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}}}
3.2.4 调整 Dockerfile 打包脚本
FROM nginx:1.15.2-alpineLABEL maintainer="梦想歌"COPY dist/ /usr/share/nginx/htmlCOPY .coding/nginx.conf /etc/nginx/conf.d/default.confEXPOSE 80
3.2.5 调整 Jenkinsfile 构建脚本
pipeline {agent anyenvironment {CODING_DOCKER_REPO_NAME = "docker"CODING_DOCKER_REPO_HOST = "${CCI_CURRENT_TEAM}-docker.pkg.${CCI_CURRENT_DOMAIN}"CODING_DOCKER_REPO_URL = "${CODING_DOCKER_REPO_HOST}/${PROJECT_NAME}/${CODING_DOCKER_REPO_NAME}/${DEPOT_NAME}"TCR_NAMESPACE_NAME = "xxx"TCR_DOCKER_REPO_URL = "shjrccr.ccs.tencentyun.com/${TCR_NAMESPACE_NAME}/${DEPOT_NAME}"}stages {stage('检出') {steps {checkout([$class: 'GitSCM',branches: [[name: GIT_BUILD_REF]],userRemoteConfigs: [[url: GIT_REPO_URL,credentialsId: CREDENTIALS_ID]]])}}stage('编译') {steps {script {if (env.TAG_NAME ==~ /.*/ ) {CODING_ARTIFACT_VERSION = "${env.TAG_NAME}"} else if (env.MR_SOURCE_BRANCH ==~ /.*/ ) {CODING_ARTIFACT_VERSION = "mr-${env.MR_RESOURCE_ID}-${env.GIT_COMMIT_SHORT}"} else {CODING_ARTIFACT_VERSION = "${env.BRANCH_NAME.replace('/', '-')}-${env.GIT_COMMIT_SHORT}"}}sh 'rm -rf /usr/lib/node_modules/npm/'dir ('/root/.cache/downloads') {sh 'wget -nc "https://coding-public-generic.pkg.coding.net/public/downloads/node-linux-x64.tar.xz?version=v16.13.0" -O node-v16.13.0-linux-x64.tar.xz | true'sh 'tar -xf node-v16.13.0-linux-x64.tar.xz -C /usr --strip-components 1'}withCredentials([usernamePassword(credentialsId: env.CODING_ARTIFACTS_CREDENTIALS_ID,usernameVariable: 'CODING_ARTIFACTS_USERNAME',passwordVariable: 'CODING_ARTIFACTS_PASSWORD')]) {script {sh '''echo "CODING_ARTIFACTS_USERNAME=${CODING_ARTIFACTS_USERNAME}" >> $CI_ENV_FILEecho "CODING_ARTIFACTS_PASSWORD=${CODING_ARTIFACTS_PASSWORD}" >> $CI_ENV_FILE'''readProperties(file: env.CI_ENV_FILE).each {key, value -> env[key] = value}}sh 'npm install'sh 'npm run build:prod'}}}stage('推送到 Docker 制品库') {steps {withEnv(["DOCKER_USERNAME=${DOCKER_USERNAME}","DOCKER_PASSWORD=${DOCKER_PASSWORD}","DOCKER_IMAGE=${TCR_DOCKER_REPO_URL}:${CODING_ARTIFACT_VERSION}"]) {sh 'docker login --username=${DOCKER_USERNAME} --password=${DOCKER_PASSWORD} shjrccr.ccs.tencentyun.com'sh 'docker build -t ${DOCKER_IMAGE} .'sh 'docker tag ${DOCKER_IMAGE} ${DOCKER_IMAGE}'sh 'docker push ${DOCKER_IMAGE}'}}}stage('部署到 K8s 集群') {steps {withEnv(["DOCKER_IMAGE=${TCR_DOCKER_REPO_URL}:${CODING_ARTIFACT_VERSION}"]) {cdDeploy(deployType: 'PATCH_IMAGE', application: '${CCI_CURRENT_TEAM}', pipelineName: '${PROJECT_NAME}-${CCI_JOB_NAME}-202222222', image: '${DOCKER_IMAGE}', cloudAccountName: 'xxx-k8s-test', namespace: 'test', manifestType: 'Deployment', manifestName: 'xxx-client', containerName: 'xxx-client', credentialId: 'aaaaaaaaaaaaaaaaaaaaaaa', personalAccessToken: '${CD_PERSONAL_ACCESS_TOKEN}')}}}}}
构建成功的效果图如下。
