本文主要以部署前端Vue项目为例,讲述了如何基于云原生DevOps服务自动化部署前端项目~从开发完成到线上环境,我们只需提交代码即可~
一、引言
作为一名开发人员,日常工作中我们除了需要负责代码的开发外,通常还需要负责代码的部署,运维等工作。而在传统的手工部署方法中,在每次版本迭代或需求变更完成后,除了将代码提交到代码仓库外,如果需要更新线上环境,我们还需要重复本地构建打包、连接远程服务器、上传代码到服务器指定目录等步骤。这些步骤虽然简单,但是通常耗时耗力,若同时存在多个环境时,也容易引起部署错误。
而随着DevOps理念、容器技术和IT自动化等理念与技术的兴起,我们可以利用现有的技术和工具,以IT自动化以及持续集成(CI)、持续部署(CD)为基础,搭建一个完整的工作流,用于优化程序开发、测试、系统运维等所有环节。如下面这个图所示,从开发完成到线上环境,我们只需提交代码即可。
接下来,将以部署前端Vue项目为例,讲述如何通过腾讯云CODING DevOps、容器镜像服务TCR、容器服务TKE和负载均衡CLB等云产品提供的强大云原生DevOps服务来实现vue项目的自动化部署,重点记录了自己的学习过程~
二、需要掌握的技术
在学习基于云原生DevOps服务自动化部署项目之前,我们需要先熟悉以下技术及工具的基本使用方法~
1、Docker
DevOps是一种理念,而其具体实现通常需要基于容器技术。而Docker作为一个开源容器项目,提供了高效、敏捷和轻量级的容器方案,并支持部署到本地环境和多种主流云平台。下面主要列举了Docker的一些常用操作:
从仓库拉取镜像 []表示可省略 |
docker [image] pull nginx:latest |
列出本机的所有镜像文件 |
docker [image] ls |
删除镜像 |
docker [image] rm nginx:latest |
上传镜像 |
docker [image] push nginx:latest |
使用Dockerfile构建镜像 -t:指定镜像的标签列表 -f:Dockerfile名称及路径 |
docker [image] build -t nginx:new -f /dockerfile_path |
运行镜像,并生成一个容器 -p:容器端口映射 -i:分配伪终端,并绑定到容器的标准输入上 -t:让容器的标准输入保持打开 -d:让容器在后台以守护态形式运行 |
docker [container] run -p 80:80 -it -d nginx:latest |
列出所有正在运行的容器 | docker [container] ps |
列出所有容器,包括终止运行的容器 | docker [container] ps -a |
启动容器 | docker [container] start container_name/container_id |
暂停容器 | docker [container] stop container_name/container_id |
删除容器 | docker [container] rm container_name/container_id |
2、Kubernetes
Kubernetes(K8S)是负责自动化运维管理多个Docker容器的集群,解决了容器的管理和调度问题,需要理解Node、Pod、Deployment、Service和Ingress等相关概念。
相关学习网站:https://kubernetes.io/zh/docs/concepts/overview/what-is-kubernetes/
3、持续集成/持续部署工具
现有的持续集成/持续部署(CI/CD)工具通常包括了Jenkins、Travis CI、Circle CI、TeamCity、CodeShip、GitLab CI和Bamboo等,由于CODING持续集成全面兼容Jenkins的持续集成服务,所以这里也使用到了Jenkins,需要掌握Jenkinsfile的使用。
4、用到的云产品
CODING DevOps:https://cloud.tencent.com/product/coding-ci
容器镜像服务TCR:https://cloud.tencent.com/product/tcr
容器服务TKE:https://cloud.tencent.com/product/tke
负载均衡CLB:https://cloud.tencent.com/product/clb
CODING DevOps能够与TCR和TKE紧密结合,当CODING代码仓库的源代码发生更新时,就能够自动触发镜像构建,并推送到TCR镜像仓库。同时,通过在TCR设置交付流水线,当TCR镜像仓库发生更新时,能够自动触发TKE集群进行部署,从而更新线上环境。
三、先从一个简单例子讲起
由于采用基于云原生的DevOps需要使用容器技术实现项目的部署,因此这里以Vue项目为例,介绍了如何采用Docker容器部署前端项目。
1、基于Docker容器部署Vue项目
前提条件:一台已经安装了Docker环境的服务器(云服务器CVM),以及一个Vue项目,并且通过npm run build生成了dist目录。
1)编写Dockerfile文件
Dockerfile文件一般存放在项目根目录,也可以自由指定存放目录。Dockerfile的内容如下:
# 前端应用部署在nginx里面,所以需要依赖nginx镜像 FROM nginx # 可以指定镜像的所有者,非必须 MAINTAINER iceshuang@tencent.com # 将构建后的资源目录复制到nginx默认的静态资源托管目录中 COPY dist/ /usr/share/nginx/html/ # 非守护态运行,使容器不会自动退出 CMD nginx -g 'daemon off;'
2)将dist目录和Dockerfile上传到服务器指定目录下
3)根据Dockerfile文件构建镜像,这里将镜像的标签名称命名为web
4)构建好镜像后,会根据这个镜像创建并启动容器,这里将容器名称命名为为vue
5)由于创建容器时绑定了端口号8080,因此可以通过ip:8080访问前端项目
2、 使用CODING DevOps创建自己的构建计划
接下来,将基于上面的基础,使用CODING DevOps创建一个完整的工作流,实现从开发完成到线上环境,只需提交代码即可。
前提条件:已经申请了CODING DevOps的团队域名和团队项目,并且已经创建并初始化了一个项目仓库(详细文档)。
1)创建Docker类制品库
选择制品管理 -> 制品仓库,创建Docker类制品库。创建成功后,点击使用访问令牌生成配置,记录下对应的登录名和密码(详细文档),如下图所示,创建了一个名为image的镜像仓库。
点击项目设置(左下角)-> 开发者选项 -> 凭据管理 -> 录入凭据,配置登录名密码生成凭据,创建成功后,会生成一个凭据ID。该凭据用于向制品库拉取和上传镜像。
2)云服务器创建SSH密钥对
在云服务器控制器创建SSH密钥对,并且将密钥对绑定到对应的云服务器(详细文档)。
点击项目设置(左下角)-> 开发者选项 -> 凭据管理 -> 录入凭据,根据上面生成的密钥信息,配置SSH密钥生成凭据,创建成功后,会生成一个凭据ID。该凭据用于登录云服务器。
3)创建Jenkinsfile文件
Jenkinsfile文件主要用于自定义持续集成流水线过程,可以根据实际情况设置Jenkinsfile的路径。这里主要在Vue项目根目录新建了deploy目录,并将Jenkinsfile和Dockerfile放入deploy中。
vue |- package.json |- README.md |- /deploy |- Dockerfile |- Jenkinsfile |- /node_modules |- .src |- .dockerignore
Dockfile内容和前面保持一致,Jenkinsfile内容如下,其中,变量DOCKER_REGISTRY_CREDENTIALS_ID、CVM_REGISTRY_CRENDENTIALS_ID、CVM_NAME、CVM_IP和CVM_USER需要在步骤4)中进行设置。
pipeline { agent any stages { stage('检出') { steps { checkout([$class: 'GitSCM', branches: [[name: GIT_BUILD_REF]], userRemoteConfigs: [[ url: GIT_REPO_URL, credentialsId: CREDENTIALS_ID ]]]) } } stage('安装依赖') { steps { sh 'npm install' } } stage('构建') { steps { sh 'npm run build' } } stage('构建镜像') { steps { echo '构建镜像...' script { sh "docker build -t icesices-docker.pkg.coding.net/vue/image/${env.GIT_LOCAL_BRANCH}-${env.CI_BUILD_NUMBER} -f ./deploy/Dockerfile ." } echo '构建镜像完成' } } stage('推送镜像') { steps { echo '推送镜像...' script { // DOCKER_REGISTRY_CREDENTIALS_ID需要在变量与缓存中进行设置 withCredentials([usernamePassword(credentialsId: env.DOCKER_REGISTRY_CREDENTIALS_ID, usernameVariable: 'REGISTRY_USER', passwordVariable: 'REGISTRY_PASS')]) { sh "docker login -u ${env.REGISTRY_USER} -p ${env.REGISTRY_PASS} icesices-docker.pkg.coding.net" sh "docker push icesices-docker.pkg.coding.net/vue/image/${env.GIT_LOCAL_BRANCH}-${env.CI_BUILD_NUMBER}" } } echo '推送镜像完成' } } stage('部署') { steps { echo '部署中...' script { // CVM_NAME 服务器名称 CVM_IP 服务器ip CVM_USER 账号名称,需要在变量与缓存中进行设置 def remote = [:] remote.name = env.CVM_NAME remote.allowAnyHosts = true remote.host = env.CVM_IP remote.port = 22 remote.user = env.CVM_USER // CVM_REGISTRY_CRENDENTIALS_ID需要在变量与缓存中进行设置,该步骤主要通过SSH登录到云服务器,并且拉取Docker镜像,启动并运行容器 withCredentials([sshUserPrivateKey(credentialsId: env.CVM_REGISTRY_CRENDENTIALS_ID, keyFileVariable: 'id_rsa')]) { remote.identityFile = id_rsa // DOCKER_REGISTRY_CREDENTIALS_ID需要在变量与缓存中进行设置 withCredentials([usernamePassword(credentialsId: env.DOCKER_REGISTRY_CREDENTIALS_ID, usernameVariable: 'REGISTRY_USER', passwordVariable: 'REGISTRY_PASS')]) { sshCommand remote: remote, command: "docker login -u ${env.REGISTRY_USER} -p ${env.REGISTRY_PASS} icesices-docker.pkg.coding.net" sshCommand remote: remote, command: "docker pull icesices-docker.pkg.coding.net/vue/image/${env.GIT_LOCAL_BRANCH}-${env.CI_BUILD_NUMBER}" sshCommand remote: remote, command: "docker stop vue | true" sshCommand remote: remote, command: "docker rm vue | true" sshCommand remote: remote, command: "docker run --name vue -p 8080:80 -d icesices-docker.pkg.coding.net/vue/image/${env.GIT_LOCAL_BRANCH}-${env.CI_BUILD_NUMBER}" } } } echo '部署完成' } } } }
4)创建构建计划
点击持续集成 -> 构建计划 -> 自定义构建过程,选择对应的仓库,并且填入Jenkinsfile的路径。
配置节点池,这里可以采用CODING提供的云主机构建(有构建限制,需要提高配额才能有更好的体验),也可以采用自定义构建节点devcloud构建(详细文档),可以根据实际需要进行选择。
设置触发规则,当代码发生更新就自动触发构建。
设置变量与缓存,这里设置的变量为在Jenkinsfile中定义的变量,其中,DOCKER_REGISTRY_CREDENTIALS_ID为制品库的推送凭据,CVM_REGISTRY_CRENDENTIALS_ID为云服务器的SSH密钥,这里的值对应了步骤2)和步骤3)中生成的凭据的ID,将对应的值复制过来就好。CVM_NAME、CVM_IP和CVM_USER分别表示云服务器的名称、IP和账号名称。
5)执行构建计划
完成上述步骤之后,当我们本地完成开发,只要推送代码到代码仓库,就能够触发构建计划,自动进行代码编译构建、镜像推送和代码部署等工作,当构建成功后,重新访问我们的网站,就能够看到线上环境更新了。同时,在制品库也能够看到每次推送的镜像。
四、进阶学习
在实际的项目中,通常我们需要部署的服务器往往不止一台,而一台服务器上通常又会运行多个Docker容器。因此,为了更加高效的管理和调度Docker容器,并且得到更加安全,独享的高性能应用制品托管分发服务,接下来,将会介绍如何基于CODING DevOps、TCR、TKE和CLB等云产品提供的的强大的云原生DevOps服务来部署前端项目。
1、容器镜像服务TCR
TCR主要用来存储镜像,使用交付流水线实现容器DevOps,从而更新TKE集群中工作负载的镜像(详细文档)。
1)创建容器镜像实例
TCR包括了个人版和企业版,企业版能够提供更加安全、独享的高性能应用制品托管分发服务,因此这里使用了企业版,需要购买后才能使用。
创建实例成功后,点击实例进入配置,并新建访问凭证。记下该访问凭证的账号密码,并在CODING DevOps的凭据管理中进行录入,从而使得CODING DevOps能够向TCR推送镜像,具体见下文CODING DevOps的步骤2)。
2)创建命名空间和镜像仓库
由于创建镜像仓库时需要选取命名空间,因此这里需要先创建命名空间,然后再创建镜像仓库,生成的仓库地址就是我们在后续中拉取和推送镜像的地址。
3)配置网络访问策略
内网访问通过配置内网访问链路,指定可以访问镜像数据的私有网络VPC,通过内网方式拉取镜像可以提高推送和拉取速度。下文TKE集群在配置TCR插件拉取镜像时,需要先配置好内网访问策略。
4)配置交付流水线
前提条件:已经按照下文的部署创建好TKE集群和工作负载,部署了容器应用。
配置交付流水线包括两种场景,第一种是推送代码后自动触发镜像构建和应用部署,第二种是本地推送镜像后自动触发部署(详细文档)。这里使用到了第二种场景。
选择容器镜像服务 -> 交付中心 -> 交付流水线,新建流水线,填写流水线的相关信息。选择需要拉取的镜像仓库及所需要更新的TKE集群。
创建成功后,在容器镜像服务 -> 运维中心 -> 触发器中可以看到自动生成了触发器。
此外,在容器镜像服务 -> 实例列表 -> 访问凭据中可以看到自动生成了流水线交付专用凭据。
完成上述步骤后,当有新的镜像推送到镜像仓库时,就会触发触发器,更新TKE集群的工作负载。同时,交付流水线也会被触发执行。
2、CODING DevOps
CODING DevOps主要用来托管代码,通过配置构建计划,实现代码编译构建,镜像构建和推送镜像到TCR(详细文档)。
前提:已经申请了CODING DevOps的团队域名和团队项目,并且已经创建并初始化了一个项目仓库。
1)创建Jenkinsfile文件
同前面的例子一样,仍然在Vue根目录中创建了deploy目录,并将Dockerfile和Jenkinsfile放入deploy中。Dockerfile的内容仍然保持不变,Jenkinsfile的内容如下:
pipeline { agent any stages { stage('检出') { steps { checkout([$class: 'GitSCM', branches: [[name: GIT_BUILD_REF]], userRemoteConfigs: [[ url: GIT_REPO_URL, credentialsId: CREDENTIALS_ID ]]]) } } stage('安装依赖') { steps { sh 'npm install' } } stage('构建') { steps { sh 'npm run build' } } stage('构建镜像') { steps { echo '构建镜像...' script { sh "docker build -t vue-web.tencentcloudcr.com/vue/image:${env.GIT_LOCAL_BRANCH}-${env.CI_BUILD_NUMBER} -f ./deploy/Dockerfile ." } echo '构建镜像完成' } } stage('推送镜像') { steps { echo '推送镜像...' script { // DOCKER_REGISTRY_CREDENTIALS_ID需要在变量与缓存中进行设置 withCredentials([usernamePassword(credentialsId: env.DOCKER_REGISTRY_CREDENTIALS_ID, usernameVariable: 'REGISTRY_USER', passwordVariable: 'REGISTRY_PASS')]) { sh "echo ${REGISTRY_PASS} | docker login -u ${REGISTRY_USER} --password-stdin vue-web.tencentcloudcr.com" sh "docker push vue-web.tencentcloudcr.com/vue/image:${env.GIT_LOCAL_BRANCH}-${env.CI_BUILD_NUMBER}" } } echo '推送镜像完成' } } } }
2)设置访问凭据
点击项目设置(左下角)-> 开发者选项 -> 凭据管理 -> 录入凭据,配置账号密码生成凭据,该凭据是推送镜像到TCR的凭据,账号密码需要在TCR中获取,具体见上文容器镜像服务TCR的步骤1)。
3)创建构建计划
点击持续集成 -> 构建计划 -> 自定义构建过程,选择代码仓库,填写Jenkinsfile的路径,创建构建计划。
创建成功后,需要进行基础信息和触发规则的配置。
此外,还需要进行变量与缓存的配置,其中,DOCKER_REGISTRY_CREDENTIALS_ID的值为步骤2)中生成的凭据ID。
3、容器服务TKE
容器服务TKE主要用于创建集群,并拉取TCR中的镜像创建工作负载,部署业务应用,对外提供服务(详细文档)。
1)创建TKE集群
根据官网的指引(详细文档)创建TKE集群,根据实际需求选择节点数量,配置安全组等。创建集群成功后,在云服务器控制台可以看到创建了相应的节点服务器(这里创建时选择了两个节点)。
2)安装TCR插件
在TKE集群中通过使用TCR插件,能够实现内网免密拉取企业版实例内容器镜像(详细文档)。但是需要TKE集群和TCR部署在同一个地域,并且在容器镜像服务 -> 访问管理 -> 内网访问中需要已经创建好了私有网络VPC,具体见上文步骤。
在组件管理中,点击新建,选择TCR容器镜像服务插件,并勾选“使用TCR插件为集群配置关联实例内网访问链路的自动解析”。
创建成功后,会在命名空间tcr-assistant-system下,自动创建名称为tcr-assistant-controller-manager的Deployment和名称为tcr-assistant-webhook-service的Service。
同时,在容器镜像服务 -> 实例列表 -> 实例 -> 访问凭证中也可以看到生成了TKE集群的专用访问凭据。
3)创建工作负载
点击工作负载 -> Deploment -> 新建,创建工作负载(详细文档)。
设置工作负载的名称和命名空间。
设置实例内容容器的名称、镜像仓库、镜像版本、镜像拉取策略和实例数量。由于这里创建的TKE集群包含了2个节点,所以创建了2个pod实例,可以根据实际情况进行设置。
访问设置,由于下文中需要通过负载均衡CLB转发流量到Node,因此这里采用了主机端口访问的方式(详细文档)。在端口映射中,由于这里希望向外提供服务的端口为80,而容器内nginx服务的端口为80,因此需要将对外服务的端口和nginx服务的端口进行映射。
创建成功后,可以看到pod实例已经成功运行,通过实例所在节点IP,可以看到pod实例运行在哪个节点上。同时,在服务与路由 -> Servide中,也可以看到在对应命名空间下生成了对应的Service。
创建好工作负载后,如果登录到节点所在的云服务器上,通过kubectl get pod -o wide查看,也可以看到pod正常运行在云服务器中。
4、负载均衡CLB
负载均衡CLB能够提供安全快捷的流量分发服务,访问流量经由CLB可以自动分配到多台云服务器上。当某台云服务器发生故障时,CLB能够将流量调度到正常的节点,从而使得服务不会发生中断(详细文档)。
1)创建负载均衡实例
2)配置监听器和规则
点击新建HTTP/HTTPS监听器,选择需要监听的端口号,创建监听器。
创建好监听器后,还需要配置转发规则,这里需要输入域名(支持IPV4地址)、URL路径和均衡方式等信息。若选择输入域名,域名需要和该CLB的VIP已经进行了绑定。
配置好转发规则后,需要进行绑定后端服务绑定,这里选择的云服务器为TKE集群中的节点,端口就是在TKE集群创建工作负载时生成的Service的主机端口。Service的主机端口可以在容器服务 -> 集群 -> 服务与路由 -> Service中查看。
配置完上述步骤之后,通过域名加端口号,就可以访问到web服务了。此外,如果服务是需要通过https访问的,还需要设置443端口的监听,并将http服务重定向到https服务,具体操作可以查看官方文档。
5、遇到的问题
问题一:当本地推送代码到CODING仓库后,交付流水线成功执行,但是集群的pod在更新时一直处于pending状态,重新访问页面不能看到更新。
原因:TKE集群默认采用滚动更新方式更新(只有新的pod运行成功,才会终止原来的pod),这里主要是因为节点的内存不足,所以无法完成pod更新。
解决方法:因为集群节点运行时K8S自身相关组件会占用一部分系统资源,所以在创建集群节点时配置不能太低。
问题二:当本地推送代码到CODING仓库后,交付流水线成功执行,但是集群对应的pod没有触发更新(即上图页面中一直只有旧的pod处于Running状态)。
原因:每次向镜像仓库推送镜像时不要使用相同的tag,对于同名的tag,会认为是同一个镜像,所以集群不会进行更新。
解决方法:在Jenkinsfile中构建镜像时可以使用CI_BUILD_NUMBER变量进行版本标记,这样每次构建镜像时镜像的tag都会不同。
sh "docker build -t vue-web.tencentcloudcr.com/vue/image:${env.GIT_LOCAL_BRANCH}-${env.CI_BUILD_NUMBER} -f ./deploy/Dockerfile ."
问题三:TKE集群创建工作负载后,怎么验证pod已经在节点上正常运行
解决方法: 方法1:可以在云服务器控制台找到服务器的公网ip,通过NodePort访问web服务是否能够正常访问
方法2:找到集群节点对应的云服务器,登录云服务器,通过运行kubectl get pod -o wide查看pod是否正常运行
问题四:负载均衡CLB能够提供流量分发服务,当其中一个节点发生故障时,能够将流量转发到正常节点,但是实现过程中,总共包括两个节点,将其中一个集群节点关机,发现web服务会中断几分钟才能访问,并且CLB对两个节点的健康检查状态都是异常(正常应该是服务不会中断,并且一个节点正常,一个节点异常)。
原因:TKE集群创建的工作负载pod实例数量小于节点数量,比如有两个集群节点,但是只创建了一个pod,这个时候pod只会运行在其中一个节点中,因此,当关机的节点刚好就是pod运行的那个节点时,需要花一些时间在另一个节点上重新创建并运行pod,pod启动成功后,CLB才会将流量调度到该节点,因此中间会有几分钟不能方法。
解决方法:TKE集群在创建pod实例时,有多少节点就应该创建至少多少个pod,比如上面创建了2个节点,就创建两个pod,并且设置反亲和性,这样能够保证每个节点至少运行一个pod,当其中一个节点发生故障时,CLB能够快速将流量转发到正常节点,不会中断服务。
问题五:开发时通常会有很多个环境,需要创建不同的镜像仓库,怎么修改Jenkinsfile才能自动根据当前分支推送镜像到对应的镜像仓库
解决方法:假设现在有develop,test和master三个不同环境,并且对应三个不同镜像仓库,可以在Jenkinsfile加入下面代码
stage('构建镜像') { steps { echo '构建镜像...' script { switch(env.GIT_LOCAL_BRANCH) { case 'develop': sh "docker build -t vue-web.tencentcloudcr.com/vue/image-develop:${env.GIT_LOCAL_BRANCH}-${env.CI_BUILD_NUMBER} -f ./deploy/Dockerfile ." break case 'test': sh "docker build -t vue-web.tencentcloudcr.com/vue/image-test:${env.GIT_LOCAL_BRANCH}-${env.CI_BUILD_NUMBER} -f ./deploy/Dockerfile ." break case 'master': sh "docker build -t vue-web.tencentcloudcr.com/vue/image-master:${env.GIT_LOCAL_BRANCH}-${env.CI_BUILD_NUMBER} -f ./deploy/Dockerfile ." break } } echo '构建镜像' } }
}
当完成上面的所有步骤之后,只要我们推送代码到CODING仓库,就能够自动执行代码构建、镜像构建、推送镜像和更新TKE集群工作负载等步骤。当我们重新访问页面时,就可以看到线上环境已经自动更新啦~