程序员是很懒的动物,因此想各类办法解决重复劳动的问题,若是你的工做流中还在重复一些事,那么可能就得想一想如何优化了前端
持续集成就是能够帮助咱们解决重复的代码构建,自动化测试,发布等重复劳动,经过简单一个提交代码的动做,解决接下来要作的不少事。vue
容器技术使这一切变得更完美。java
典型的一个场景:node
咱们写一个前端的工程,假设是基于vue.js的框架开发的,提交代码以后但愿跑一跑测试用例,而后build压缩一个到dist目录里,再把这个目录的静态文件用nginx代理一下。
最后打成docker镜像放到镜像仓库。 甚至还能够增长一个在线上运行起来的流程。nginx
如今告诉你,只须要一个git push动做,接下来全部的事CI工具会帮你解决!这样的系统若是你还没用上的话,那请问还在等什么。接下来会系统的向你们介绍这一切。git
首先SVN这种渣渣软件就该尽早淘汰,没啥好说的,有git真的没有SVN存在的必要了我以为。程序员
因此咱们选一个git仓库,git仓库比较多,我这里选用gogs,gitea gitlab都行,根据需求自行选择github
docker run -d --name gogs-time -v /etc/localtime:/etc/localtime -e TZ=Asia/Shanghai --publish 8022:22 \ --publish 3000:3000 --volume /data/gogs:/data gogs:latest
访问3000端口,而后就没有而后了
gogs功能没有那么强大,不过占用资源少,速度快,咱们稳定运行了几年了。缺点就是API不够全。golang
当你用过drone以后。。。web
装:
version: '2' services: drone-server: image: drone/drone:0.7 ports: - 80:8000 volumes: - /var/lib/drone:/var/lib/drone/ restart: always environment: - DRONE_OPEN=true - DOCKER_API_VERSION=1.24 - DRONE_HOST=10.1.86.206 - DRONE_GOGS=true - DRONE_GOGS_URL=http://10.1.86.207:3000/ # 代码仓库地址 - DRONE_SECRET=ok drone-agent: image: drone/drone:0.7 command: agent restart: always depends_on: - drone-server volumes: - /var/run/docker.sock:/var/run/docker.sock environment: - DOCKER_API_VERSION=1.24 - DRONE_SERVER=ws://drone-server:8000/ws/broker - DRONE_SECRET=ok
docker-compose up -d
而后你懂的,也没有而后了
用gogs帐户登陆drone便可
每一个步骤就是个容器,每一个插件也是个容器,各类组合,简直就是活字印刷术
怎么使用这种初级肤浅的内容我就不赘述了,可是有不少坑的地方:
安装方式2,在k8s上安装:
helm install stable/drone
首先在你的代码仓库主目录下新建三个文件:
用gogs帐户密码登陆到drone页面上以后同步下项目就能够看到项目列表,打开开关就能够关联到git仓库,比较简单,自行探索
pipeline: backend: # 一个步骤的名称,能够随便全名 image: golang # 每一个步骤的本质都是基于这个镜像去启动一个容器 commands: # 在这个容器中执行一些命令 - go get - go build - go test frontend: image: node:6 commands: - npm install - npm test publish: image: plugins/docker repo: octocat/hello-world tags: [ 1, 1.1, latest ] registry: index.docker.io notify: image: plugins/slack channel: developers username: drone
各步骤启动的容器共享workdir这个卷, 这样build步骤的结果产物就能够在publish这个容器中使用
结合Dockerfile看:
# docker build --rm -t drone/drone . FROM drone/ca-certs EXPOSE 8000 9000 80 443 ENV DATABASE_DRIVER=sqlite3 ENV DATABASE_CONFIG=/var/lib/drone/drone.sqlite ENV GODEBUG=netdns=go ENV XDG_CACHE_HOME /var/lib/drone ADD release/drone-server /bin/ # 由于工做目录共享,因此就能够在publish时使用到 build时的产物,这样构建和发布就能够分离 ENTRYPOINT ["/bin/drone-server"]
上面说到构建与发布分离,颇有用,如构建golang代码时咱们须要go环境,可是线上或者运行时其实只须要一个可执行文件便可,
因此Dockerfile里就能够不用FROM一个golang的基础镜像,让你的镜像更小。又好比java构建时须要maven,而线上运行时不须要,
因此也是能够分离。
用drone时要发挥想象,千万不要用死了,上面每句话都须要仔细读一遍,细细理解。再总结一下关键点:
drone自身是无论每一个步骤是什么功能的,只傻瓜式帮你起容器,跑完正常就执行下个步骤,失败就终止。
编译,提交到镜像仓库,部署,通知等功能都是由镜像的功能,容器的功能决定的 drone里叫插件,插件本质就是镜像,有一丢丢小区别后面说
这意味着你想干啥就弄啥镜像,如编译时须要maven,那去作个maven镜像,部署时须要对接k8s,那么搞个有kubectl客户端的镜像;要物理机部署那么搞个
ansible的镜像,等等,发挥想象,灵活使用。
有时咱们但愿CI出来的docker镜像tag与git的tag一致,这样的好处就是知道运行的是哪一个版本的代码,升级等等都很方便,不过每次都去修改pipeline
文件显然很烦,那么drone就能够有不少环境变量来帮助咱们解决这个问题:
pipeline: build: image: golang:1.9.2 commands: - go build -o test --ldflags '-linkmode external -extldflags "-static"' when: event: [push, tag, deployment] publish: image: plugins/docker repo: fanux/test tags: ${DRONE_TAG=latest} dockerfile: Dockerfile insecure: true when: event: [push, tag, deployment]
这个例子${DRONE_TAG=latest}
若是git tag事件触发了pipeline那就把git tag当镜像tag,不然就用latest,这样咱们本身研发过程当中就
能够一直用latest迭代,以为版本差很少了,打个tag,生成一个能够给测试人员测试的镜像,很是优雅,不须要改什么东西,不容易出错
同理还有不少其它的环境变量能够用,如git的commitID 分支信息等等, 这里能够查
首先得有个k8s集群,那么首选:kubernetes集群三步安装 广告,无视就好。。。
有了上面的铺垫,对接k8s就至关简单了:搞个kubectl的镜像嵌入流程中便可:
把项目的k8s yaml文件放到代码中,而后pipelie里直接apply
publish: image: plugins/docker # 镜像仓库,执行Dockerfile插件 tags: - ${DRONE_TAG=latest} insecure: true # 照抄 deploy: image: kubectl:test # 这个镜像本身去打便可 commands: - cat test.yaml - ls - rm -rf /root/.kube && cp -r .kube /root # k8s 的kubeconfig文件,能够有多个,部署到哪一个集群就拷贝哪一个kubeconfig文件 - kubectl delete -f test.yaml || true - kubectl apply -f test.yaml
不过最佳实践还有几个细节:
因此咱们引入chart, 用helm进行部署:
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: test namespace: {{ .Values.namespace }} spec: replicas: {{ .Values.replicaCount }} template: metadata: labels: name: test spec: serviceAccountName: test containers: - name: test image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" # deployment的yaml文件是模板,建立时再传参进来渲染 imagePullPolicy: {{ .Values.image.pullPolicy }} ....
注意,有了模板以后,咱们部署v1版本和v2版本时就不须要改动yaml文件,这样下降出错风险,pipeline执行时把环境变量传进来,完美解决
这样git tag 镜像tag与yaml里镜像配置实现了彻底的统一:
deploy_dev: # 部署到开发环境 image: helm:v2.8.1 commands: - mkdir -p /root/.kube && cp -r .kube/config-test101.194 /root/.kube - helm delete test --purge || true - helm install --name test --set image.tag=${DRONE_TAG=latest} Chart when: event: deployment environment: deploy_dev deploy_test: # 部署到测试环境 image: helm:v2.8.1 commands: - mkdir -p /root/.kube && cp -r .kube/config-test101.84 /root/.kube # 两个环境使用不一样的kubeconfig - helm delete test --purge || true - helm install --name test --set image.tag=${DRONE_TAG=latest} Chart # 把git tag传给helm,这样运行的镜像就是publish时构建的镜像,tag一致 when: event: deployment environment: deploy_test
以上,优雅的解决了上面问题
细节:event能够是git的事件也能够是手动处罚的事件,类型是deployment时就是手动触发的,drone支持命令行触发
咱们进行了二次开发,让drone能够在页面上触发对应的事件
drone上开通一个仓库时,会给仓库设置一个webhook,在项目设置里能够看到,这样git的事件就能够通知到drone,drone根据事件去拉取代码走流程
理解原理对使用这个系统很是重要,不然就会把一个东西用死。
pipeline就负责起容器而已,容器干啥的系统不关心,用户决定 这句话本文不止强调过一次,很是重要多读几遍
镜像即插件,也就是可能现有不少镜像都能直接看成插件嵌入到drone流程中。
有个小区别是,你会发现drone有些插件还带一些参数,这就是比普通的镜像多作了一丢丢事,如publish时打docker的镜像:
publish: image: plugins/docker repo: octocat/hello-world tags: [ 1, 1.1, latest ] registry: index.docker.io
你会发现它有 repo tags什么的参数,其实drone处理时很是简单,就是把这些参数转化成环境变量传给容器了,
而后容器去处理这些参数。
本质就是作了这个事情:
docker run --rm \ -e PLUGIN_TAG=latest \ -e PLUGIN_REPO=octocat/hello-world \ -e DRONE_COMMIT_SHA=d8dbe4d94f15fe89232e0402c6e8a0ddf21af3ab \ -v $(pwd):$(pwd) \ -w $(pwd) \ --privileged \ plugins/docker --dry-run
那咱们自定义一个插件就简单了,只要写个脚本能处理特定环境变量便可,如一个curl的插件:
pipeline: webhook: image: foo/webhook url: http://foo.com method: post body: | hello world
写个脚本
#!/bin/sh curl \ -X ${PLUGIN_METHOD} \ # 处理一个几个环境变量 -d ${PLUGIN_BODY} \ ${PLUGIN_URL}
FROM alpine ADD script.sh /bin/ RUN chmod +x /bin/script.sh RUN apk -Uuv add curl ca-certificates ENTRYPOINT /bin/script.sh
docker build -t foo/webhook . docker push foo/webhook
打成docker镜像,大功告成
因此大部分状况咱们会很懒的什么也不写,直接在容器里执行命令就是了,一样是一个curl的需求,不写插件的话
pipeline: webhook: image: busybox # 直接用busybox command: - curl -X POST -d 123 http://foo.com 完事,插件都懒得开发了
值得注意的是一些复杂功能仍是须要开发插件的,如publish镜像时用的插件。关于该插件我想补充一句
它是docker里面起了一个docker engine,用docker内的docker engine进行打镜像的
因此devicemapper存储驱动是支持不了的。请升级内核用overlay2,或者ubuntu用aufs
要实现高效的自动化,everything as code很重要,不少人喜欢在界面上点点点 填不少参数上线,实际上是一种很容易出错的方式
不必定能提升效率。 大家项目如何构建,如何发布,如何部署都应该是代码,没有二义性,把人作的事让程序作,最终人仅是触发而已。
我的看法,探讨可加QQ群:98488045