k8s上使用流水线部署应用
1. 部署流程
- 为每一个项目准备一个Dockerfile;Docker按照这个Dockerfile将项目制作成镜像
- 为每一个项目生成k8s的部署描述文件
- Jenkins编写好Jenkinsfile
2. 抽取生产环境配置
以gulimall-cart 服务为例
-
首先创建application-prod.properties 文件,也可以使用yaml,但是我觉得yaml对格式的要求太高了就使用properties -
将之前的yaml文件转为properties文件,再复制到application-prod.properties ,此处我使用的在线网站的方式https://www.toyaml.com/index.html -
更换成功后先测试本地环境是否能起来,排查是否有转换上的问题 -
再将本地测试的mysql,redis,elasticsearch,nacos,rabbitmq,zipkin,sentinel链接换成线上的域名 -
测试成功后再将所有服务的ip做域名替换 -
将所有服务的正式环境端口都设为8080或者你喜欢的 原因:这样的话我们就不需要在部署的时候关注服务本来的端口,服务被打成了镜像,在不同的容器中使用相同的端口是被允许的.而我们访问服务是通过注册中心,只关注服务名.
3. 开通阿里云容器镜像服务
此处设置的密码就是以后的登录密码
创建镜像仓库,选择代码源未本地仓库
3.1 本镜像仓库操作指南
3.1.1 登录阿里云Docker Registry
$ docker login --username=院长大人9 registry.cn-hangzhou.aliyuncs.com
用于登录的用户名为阿里云账号全名,密码为开通服务时设置的密码。
您可以在访问凭证页面修改凭证密码。
3.1.2 从Registry中拉取镜像
$ docker pull registry.cn-hangzhou.aliyuncs.com/zr-dev/gulimall-dev:[镜像版本号]
3.1.3 将镜像推送到Registry
$ docker login --username=院长大人9 registry.cn-hangzhou.aliyuncs.com
$ docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/zr-dev/gulimall-dev:[镜像版本号]
$ docker push registry.cn-hangzhou.aliyuncs.com/zr-dev/gulimall-dev:[镜像版本号]
请根据实际镜像信息替换示例中的[ImageId]和[镜像版本号]参数。
3.1.4 选择合适的镜像仓库地址
从ECS推送镜像时,可以选择使用镜像仓库内网地址。推送速度将得到提升并且将不会损耗您的公网流量。
如果您使用的机器位于VPC网络,请使用 registry-vpc.cn-hangzhou.aliyuncs.com 作为Registry的域名登录。
3.1.5 示例
使用"docker tag"命令重命名镜像,并将它通过专有网络地址推送至Registry。
$ docker imagesREPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZEregistry.aliyuncs.com/acs/agent 0.7-dfb6816 37bb9c63c8b2 7 days ago 37.89 MB$ docker tag 37bb9c63c8b2 registry-vpc.cn-hangzhou.aliyuncs.com/acs/agent:0.7-dfb6816
使用 “docker push” 命令将该镜像推送至远程。
$ docker push registry-vpc.cn-hangzhou.aliyuncs.com/acs/agent:0.7-dfb6816
4. 创建微服务Dockerfile
#构建的镜像设置基础镜像
FROM java:8
#为构建的镜像设置监听端口
EXPOSE 8080
#指定镜像内的目录为数据卷
VOLUME /tmp
#构建镜像时,复制上下文中的文件到镜像内
ADD target/*.jar /app.jar
#在镜像的构建过程中执行特定的命令,并生成一个中间镜像
RUN bash -c 'touch /app.jar'
#指定镜像的执行程序
ENTRYPOINT ["java","-jar","-Xms128m","-Xmx300m","/app.jar","--spring.profiles.active=prod"]
4.1 打包镜像推送
当我们手动打包将jar与Dockerfile放同一个目录时,需要修改Dockerfile的ADD target/*.jar 修改目录位置
docker build -f Dockerfile -t docker.io/zr/admin:v1.0 .
打包成功后查看docker镜像
由于我们需要将镜像推送至阿里云镜像,所以我们需要参照阿里云前缀为镜像重新打标签,顺便新建一个gulimall-admin仓库
docker tag 68dd4281cbc0 registry.cn-hangzhou.aliyuncs.com/zr-dev/gulimall-admin:v1.0
标签打成功后发现他们的镜像id都是相同的
最后推送至镜像仓库,推送后自动帮我们创建对应仓库
docker push registry.cn-hangzhou.aliyuncs.com/zr-dev/gulimall-admin:v1.0
5. 创建微服务k8s部署描述文件
5.0 理解targetport,port,nodeport
个人理解:
targetport:容器暴露的端口
port:容器在service中暴露的端口(集群中可访问的端口)
nodeport:容器在服务器中暴露的端口(外网可访问的端口)
以gulimall-auth-server为例,需要部署的所有文件都需要该描述文件并放在各自服务的相同名称目录下
5.1 在服务下新建一个相关部署文件
kind: Deployment
apiVersion: apps/v1
metadata:
name: gulimall-auth-server
namespace: gulimall
labels:
app: gulimall-auth-server
spec:
replicas: 1
selector:
matchLabels:
app: gulimall-auth-server
template:
metadata:
labels:
app: gulimall-auth-server
spec:
containers:
- name: gulimall-auth-server
image: $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:latest
ports:
- containerPort: 8080
protocol: TCP
resources:
limits:
cpu: 1000m
memory: 500Mi
requests:
cpu: 10m
memory: 10Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullPolicy: IfNotPresent
restartPolicy: Always
terminationGracePeriodSeconds: 30
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25%
maxSurge: 25%
revisionHistoryLimit: 10
progressDeadlineSeconds: 600
---
kind: Service
apiVersion: v1
metadata:
name: gulimall-auth-server
namespace: gulimall
labels:
app: gulimall-auth-server
spec:
ports:
- name: http
protocol: TCP
port: 8080
targetPort: 8080
nodePort: 31000
selector:
app: gulimall-auth-server
type: NodePort
sessionAffinity: None
5.2 在其他服务创建部署文件
在其他服务的相同位置创建对应服务名的yaml文件,我这里是用的[servicename]/deploy/[servicename]-deploy.yaml,将之前的gulimall-auth-server-deploy.yaml 复制到目录下改名后需要修改下图内容及nodePort 不要有重复(因为之前我们为所有服务的端口都为8080所以不用修改)
nodePort 最好在原来的基础上多加几个,给后期集群预留端口
6. Jenkins编写好Jenkinsfile(自定义流水线)
流水线 (jenkins.io)
6.1 k8s创建流水线
设置代理类型node ,lable为maven
6.2 流水线第一步–gitee拉取代码
gitee-id
创建码云凭证
对应的Jenkinsfile
pipeline {
agent {
node {
label 'maven'
}
}
stages {
stage('拉取代码') {
steps {
git(url: 'https://gitee.com/zhourui815/gulimall.git', credentialsId: 'gitee-id', branch: 'master', changelog: true, poll: false)
}
}
}
}
6.3 流水线第二步–参数化构建环境变量
作用:
1.可以接受传递进来的参数
2.可以在外面定义参数的值类型
pipeline {
agent {
node {
label 'maven'
}
}
stages {
stage('拉取代码') {
steps {
git(url: 'https://gitee.com/zhourui815/gulimall.git', credentialsId: 'gitee-id', branch: 'master', changelog: true, poll: false)
sh 'echo 正在构建 $PROJECT_NAME 版本号:$PROJECT_VERSION 将会提交给 $REGISTRY 镜像仓库'
}
}
}
parameters {
string(name:'PROJECT_VERSION',defaultValue: 'v0.0Beta',description:'项目版本')
string(name:'PROJECT_NAME',defaultValue: 'gulimall-gateway',description:'构建模块')
}
}
运行时输入参数
6.4 流水线第三步–sonar代码质量分析
Jenkinsfile
pipeline {
agent {
node {
label 'maven'
}
}
environment {
DOCKER_CREDENTIAL_ID = 'alidockerhub'
GITEE_CREDENTIAL_ID = 'gitee-id'
KUBECONFIG_CREDENTIAL_ID = 'demo-kubeconfig'
REGISTRY = 'registry.cn-hangzhou.aliyuncs.com'
DOCKERHUB_NAMESPACE = 'zr-dev'
GITEE_ACCOUNT = 'zhourui815'
SONAR_CREDENTIAL_ID = 'sonar-qube'
BRANCH_NAME = 'master'
}
stages {
stage('拉取代码') {
steps {
git(url: 'https://gitee.com/zhourui815/gulimall.git', credentialsId: 'gitee-id', branch: 'master', changelog: true, poll: false)
sh 'echo 正在构建 $PROJECT_NAME 版本号:$PROJECT_VERSION 将会提交给 $REGISTRY 镜像仓库'
}
}
stage('代码质量分析') {
steps {
container ('maven') {
withCredentials([string(credentialsId: "$SONAR_CREDENTIAL_ID", variable: 'SONAR_TOKEN')]) {
withSonarQubeEnv('sonar') {
sh "echo 当前目录 `pwd`"
sh "mvn clean install -Dmaven.test.skip=true"
sh "mvn sonar:sonar -gs `pwd`/mvn-settings.xml -Dsonar.branch=$BRANCH_NAME -Dsonar.login=$SONAR_TOKEN"
}
}
timeout(time: 1, unit: 'HOURS') {
waitForQualityGate abortPipeline: true
}
}
}
}
}
parameters {
string(name:'PROJECT_VERSION',defaultValue: 'v0.0Beta',description:'项目版本')
string(name:'PROJECT_NAME',defaultValue: 'gulimall-gateway',description:'构建模块')
}
}
由于sonar需要一个mvn-setting文件,其实就是为sonar设置maven镜像仓库
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NZKq4JLz-1646796170903)(http://zr.zhourui.site/img/image-20220228160913784.png)]
mvn-setting.xml放在父项目下,根据(sh "mvn sonar:sonar -gs `pwd`/mvn-settings.xml)来指定位置
<settings>
<mirrors>
<mirror>
<id>aliyun</id>
<name>aliyun Maven</name>
<mirrorOf>*</mirrorOf>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
</mirrors>
<profiles>
<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
</profiles>
</settings>
登录sonarqube控制台查看http://10.0.0.41:32112/
6.5 流水线第四步–构建镜像&推送快照镜像
配置阿里云镜像仓库凭证
此处我配置阿里云账号与密码
Jenkinsfile
pipeline {
agent {
node {
label 'maven'
}
}
environment {
DOCKER_CREDENTIAL_ID = 'alidockerhub'
GITEE_CREDENTIAL_ID = 'gitee-id'
KUBECONFIG_CREDENTIAL_ID = 'demo-kubeconfig'
REGISTRY = 'registry.cn-hangzhou.aliyuncs.com'
DOCKERHUB_NAMESPACE = 'zr-dev'
GITEE_ACCOUNT = 'zhourui815'
SONAR_CREDENTIAL_ID = 'sonar-qube'
BRANCH_NAME = 'master'
}
stages {
stage('拉取代码') {
steps {
git(url: 'https://gitee.com/zhourui815/gulimall.git', credentialsId: 'gitee-id', branch: 'master', changelog: true, poll: false)
sh 'echo 正在构建 $PROJECT_NAME 版本号:$PROJECT_VERSION 将会提交给 $REGISTRY 镜像仓库'
}
}
stage('代码质量分析') {
steps {
container ('maven') {
withCredentials([string(credentialsId: "$SONAR_CREDENTIAL_ID", variable: 'SONAR_TOKEN')]) {
withSonarQubeEnv('sonar') {
sh "echo 当前目录 `pwd`"
sh "mvn clean install -Dmaven.test.skip=true"
sh "mvn sonar:sonar -gs `pwd`/mvn-settings.xml -Dsonar.branch=$BRANCH_NAME -Dsonar.login=$SONAR_TOKEN"
}
}
timeout(time: 1, unit: 'HOURS') {
waitForQualityGate abortPipeline: true
}
}
}
}
stage ('构建镜像 & 推送快照镜像') {
steps {
container ('maven') {
sh 'mvn -Dmaven.test.skip=true -gs `pwd`/mvn-settings.xml clean package'
sh 'cd $PROJECT_NAME && docker build -f Dockerfile -t $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:SNAPSHOT-$BRANCH_NAME-$BUILD_NUMBER .'
withCredentials([usernamePassword(passwordVariable : 'DOCKER_PASSWORD' ,usernameVariable : 'DOCKER_USERNAME' ,credentialsId : "$DOCKER_CREDENTIAL_ID" ,)]) {
sh 'echo "$DOCKER_PASSWORD" | docker login $REGISTRY -u "$DOCKER_USERNAME" --password-stdin'
sh 'docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:SNAPSHOT-$BRANCH_NAME-$BUILD_NUMBER'
}
}
}
}
}
parameters {
string(name:'PROJECT_VERSION',defaultValue: 'v0.0Beta',description:'项目版本')
string(name:'PROJECT_NAME',defaultValue: 'gulimall-gateway',description:'构建模块')
}
}
6.6 流水线第五步–部署服务
6.6.1 动态指定模块部署
在部署时无法读取到变量
将英文单引号换成英文双引号,即可读取到变量
Jenkinsfile
stage('部署到k8s') {
when{
branch 'master'
}
steps {
input(id: "deploy-to-dev-$PROJECT_NAME", message: '是部署到k8s?')
sh 'echo 正在部署 $PROJECT_NAME'
kubernetesDeploy(configs: "$PROJECT_NAME/deploy/**", enableConfigSubstitution: true, kubeconfigId: "$KUBECONFIG_CREDENTIAL_ID")
}
}
实现效果
部署文件
gulimall-gateway / deploy / gulimall-gateway-deploy.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
name: gulimall-gateway
namespace: gulimall
labels:
app: gulimall-gateway
spec:
replicas: 1
selector:
matchLabels:
app: gulimall-gateway
template:
metadata:
labels:
app: gulimall-gateway
spec:
containers:
- name: gulimall-gateway
image: $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:latest
ports:
- containerPort: 8080
protocol: TCP
resources:
limits:
cpu: 1000m
memory: 500Mi
requests:
cpu: 10m
memory: 10Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullPolicy: IfNotPresent
restartPolicy: Always
terminationGracePeriodSeconds: 30
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25%
maxSurge: 25%
revisionHistoryLimit: 10
progressDeadlineSeconds: 600
---
kind: Service
apiVersion: v1
metadata:
name: gulimall-auth-server
namespace: gulimall
labels:
app: gulimall-auth-server
spec:
ports:
- name: http
protocol: TCP
port: 8080
targetPort: 8080
nodePort: 31012
selector:
app: gulimall-auth-server
type: NodePort
sessionAffinity: None
6.7 流水线第六步–发布版本(流水线结束)
版本发布
需要配置自己的git相关配置
完整Jenkinsfile
pipeline {
agent {
node {
label 'maven'
}
}
stages {
stage('拉取代码') {
steps {
git(url: 'https://gitee.com/zhourui815/gulimall.git', credentialsId: 'gitee-id', branch: 'master', changelog: true, poll: false)
sh 'echo 正在构建 $PROJECT_NAME 版本号:$PROJECT_VERSION 将会提交给 $REGISTRY 镜像仓库'
container('maven') {
sh 'mvn clean install -Dmaven.test.skip=true -gs `pwd`/mvn-settings.xml'
}
}
}
stage('代码质量分析') {
steps {
container('maven') {
withCredentials([string(credentialsId: "$SONAR_CREDENTIAL_ID", variable: 'SONAR_TOKEN')]) {
withSonarQubeEnv('sonar') {
sh 'echo 当前目录 `pwd`'
sh "mvn sonar:sonar -gs `pwd`/mvn-settings.xml -Dsonar.branch=$BRANCH_NAME -Dsonar.login=$SONAR_TOKEN"
}
}
timeout(time: 1, unit: 'HOURS') {
waitForQualityGate true
}
}
}
}
stage('构建镜像 & 推送快照镜像') {
steps {
container('maven') {
sh 'mvn -Dmaven.test.skip=true -gs `pwd`/mvn-settings.xml clean package'
sh 'cd $PROJECT_NAME && docker build -f Dockerfile -t $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:SNAPSHOT-$BRANCH_NAME-$BUILD_NUMBER .'
withCredentials([usernamePassword(passwordVariable : 'DOCKER_PASSWORD' ,usernameVariable : 'DOCKER_USERNAME' ,credentialsId : "$DOCKER_CREDENTIAL_ID" ,)]) {
sh 'echo "$DOCKER_PASSWORD" | docker login $REGISTRY -u "$DOCKER_USERNAME" --password-stdin'
sh 'docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:SNAPSHOT-$BRANCH_NAME-$BUILD_NUMBER'
}
}
}
}
stage('推送最新镜像') {
when {
branch 'master'
}
steps {
container('maven') {
sh 'docker tag $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:SNAPSHOT-$BRANCH_NAME-$BUILD_NUMBER $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:latest '
sh 'docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:latest '
}
}
}
stage('部署到k8s') {
when{
branch 'master'
}
steps {
input(id: "deploy-to-dev-$PROJECT_NAME", message: '是部署到k8s?')
sh 'echo 正在部署 $PROJECT_NAME'
kubernetesDeploy(configs: "$PROJECT_NAME/deploy/**", enableConfigSubstitution: true, kubeconfigId: "$KUBECONFIG_CREDENTIAL_ID")
}
}
stage('发布版本'){
when{
expression{
return params.PROJECT_VERSION =~ /v.*/
}
}
steps {
container ('maven') {
input(id: 'release-image-with-tag', message: '是否发布当前版本?')
sh 'docker tag $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:SNAPSHOT-$BRANCH_NAME-$BUILD_NUMBER $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:$PROJECT_VERSION '
sh 'docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$PROJECT_NAME:$PROJECT_VERSION'
withCredentials([usernamePassword(credentialsId: "$GITEE_CREDENTIAL_ID",
passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME')]) {
sh 'git config --global user.email "2437264464@qq.com" '
sh 'git config --global user.name "eric_zhou" '
sh 'git tag -a $PROJECT_NAME-$PROJECT_VERSION -m "$PROJECT_VERSION" '
sh 'git push http://$GIT_USERNAME:$GIT_PASSWORD@gitee.com/$GITEE_ACCOUNT/gulimall.git --tags --ipv4'
}
}
}
}
}
environment {
DOCKER_CREDENTIAL_ID = 'alidockerhub'
GITEE_CREDENTIAL_ID = 'gitee-id'
KUBECONFIG_CREDENTIAL_ID = 'demo-kubeconfig'
REGISTRY = 'registry.cn-hangzhou.aliyuncs.com'
DOCKERHUB_NAMESPACE = 'zr-dev'
GITEE_ACCOUNT = 'zhourui815'
SONAR_CREDENTIAL_ID = 'sonar-qube'
BRANCH_NAME = 'master'
}
parameters {
string(name: 'PROJECT_VERSION', defaultValue: 'v0.0Beta', description: '项目版本')
string(name: 'PROJECT_NAME', defaultValue: 'gulimall-gateway', description: '构建模块')
}
}
最终效果
git上有对应的标签版本,镜像仓库中也会有对应的版本,并且最新的版本也是该版本
7. 读取远程代码中的流水线部署
7.1 第一次执行参数输入不显示,报错问题
当我们运行分支时需要填写参数,如下图但第一次运行不会弹出来
原因:因为我们输入参数是在拉取代码之前,还没有拉取到代码,Jenkinsfile中的参数配置自然没有办法生效
解决:只需要在第一次执行后拉取代码步骤后执行后,再次运行即可
|