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 any
environment {
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-alpine
LABEL maintainer="梦想歌"
COPY dist/ /usr/share/nginx/html
COPY .coding/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
3.2.5 调整 Jenkinsfile 构建脚本
pipeline {
agent any
environment {
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_FILE
echo "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}')
}
}
}
}
}
构建成功的效果图如下。