以Spring boot項目爲例。傳統方式是本地生成jar包,FTP上傳服務器,重啓服務;若是是內網測試服,也能夠在服務器上安裝git,在服務器上編譯打包。但這都須要人爲干預,因而CI/CD
就出現了。html
CI/CD的工具備不少,最流行的當屬jenkins
。不過以筆者爲數很少的經驗來看,做爲後起之秀的gitlab更簡單一點,也更靈活,不會像jenkins那樣笨重。固然,二者的概念都是挺多的,沒有師父,光靠本身入門都不容易。java
GitLab-CI/CD流程示例git
從左往右看,首先研發人員完成需求提交代碼到 GitLab。GitLab 觸發一次 Build,構建好服務,而後開始跑單元測試、集成測試。等待測試結果經過後,再由負責該項目的同事進行 CodeReview,灰度發佈,正式部署到線上。redis
本文基於GitLab 13.7版本spring
Pipelines comprise:docker
Jobs are executed by runners
. Multiple jobs in the same stage are executed in parallel, if there are enough concurrent runners.shell
上述包含了GitLab-DevOps整個流程的全部環節,CI/CD只是其中的一部分。npm
能夠安裝在任意機子上,經過它能夠[在一臺機子上]註冊多個runner
實例到gitlab服務器。每一個runner用於執行一個或多個具體任務(如build、test)。vim
runner有如下三類,可用範圍從大到小segmentfault
Shared runners
are available to all groups and projects in a GitLab instance.Group runners
are available to all projects and subgroups in a group.Specific runners
are associated with specific projects. Typically, specific runners are used for one project at a time.咱們能夠直接安裝GitLab Runner到宿主機,也可使用docker方式安裝。注意這兩種方式會影響到後續.gitlab-ci.yml
中對pipline
的定義。好比要編譯maven項目,若是executor
設爲shell,那麼若宿主機中安裝有mvn
命令,前者能夠在scripts中直接使用mvn,然後者並不能,只能經過定義Dockerfile,在其中定義搭建mvn環境到編譯代碼的整個流程。
採用docker方式安裝的話,能夠參考Docker搭建本身的Gitlab CI Runner
docker pull gitlab/gitlab-runner:latest
docker run -d --name inkscreen-api-runner --restart always \ -v /srv/gitlab-runner/config:/etc/gitlab-runner \ -v /var/run/docker.sock:/var/run/docker.sock \ gitlab/gitlab-runner:latest
註冊runner實例
docker exec -it inkscreen-api-runner gitlab-runner register
會讓咱們填一系列配置項,以下:
Enter the GitLab instance URL (for example, https://gitlab.com/): http://192.168.1.26:9980/ Enter the registration token: cJMXGJWx7qx9AmpSc6ee Enter a description for the runner: [a0debaaf80a9]: runner for InkScreen-API project Enter tags for the runner (comma-separated): InkScreenAPI Registering runner... succeeded runner=cJMXGJWx Enter an executor: docker-ssh, shell, docker-ssh+machine, kubernetes, custom, parallels, ssh, virtualbox, docker+machine, docker: docker Enter the default Docker image (for example, ruby:2.6): maven:3-jdk-8
完過後,咱們在gitlab->xxxProjct中就能找到該runner:
接下來,就能夠定義項目構建流程了。項目的構建流程是由項目根目錄的.gitlab-ci.yml
文件控制的。固然了,一個pipeline能夠涉及到多個runner。
定義一個pipline,如下爲示例
variables: DOCKER_TLS_CERTDIR: "/certs" # stage也能夠自定義 stages: - build jar # - test - build and run image #job's name 能夠隨意取 buildJar: stage: build jar variables: # 若要使cache生效,須指定-Dmaven.repo.local MAVEN_OPTS: "-Dmaven.repo.local=.m2" cache: key: ${CI_COMMIT_REF_SLUG} paths: - .m2 only: - dev script: # package 已包含 test 步驟,因此流程中不須要另外配置test job - mvn clean package tags: - inkscreen_api artifacts: paths: - target/admin.jar expire_in: 3600 seconds deploy: stage: build and run image image: docker:stable services: - docker:dind only: - dev variables: IMAGE_NAME: newton/inkscreen-api:$CI_COMMIT_REF_NAME PORT: 38082 script: - docker build --build-arg JAR_PATH=target/admin.jar -t $IMAGE_NAME . - docker run -p $PORT:$PORT -d --name inkscreen-$CI_COMMIT_REF_NAME --env spring.redis.host=myredis $IMAGE_NAME tags: - inkscreen_api
cache
cache
經常使用在dacker-based job之間傳遞文件。好比項目依賴的公共jar包,jobA辛辛苦苦從網上down了下來,結果運行完了,jobA所在容器也跟着被移除,天然裏面的全部文件都不存在了。後續其它job用到相同的jar包還要從新下載。一樣的,pipline屢次執行,jobA本身每次也要從新下載。
爲了解決這個問題,gitlab-ci採用了cache的方式。指定文件/目錄,每次job結束前將其打包,放到/etc/gitlab-runner/config.toml
中對應的[runners.docker][volumes]指定的卷內,其它job(包括本身)運行前,對應的cache都會被加載並解壓到容器內。
artifacts
artifacts
是job生成的中間產物,會以壓縮包(.zip)的形式生成。它會自動上傳到gitlab服務器,the artifacts will be downloaded and extracted in the context of later stages。因此它和cache很像,可是設計它們的初衷是不一樣的。
Don't use caching for passing artifacts between stages, as it is designed to store runtime dependencies needed to compile the project:
cache
: For storing project dependencies
Caches are used to speed up runs of a given job in subsequent pipelines, by storing downloaded dependencies so that they don't have to be fetched from the internet again (like npm packages, Go vendor packages, etc.) While the cache could be configured to pass intermediate build results between stages, this should be done with artifacts instead.
artifacts
: Use for stage results that will be passed between stages.
Artifacts are files generated by a job which are stored and uploaded, and can then be fetched and used by jobs in later stages of the same pipeline. In other words, you can't create an artifact in job-A in stage-1, and then use this artifact in job-B in stage-1. This data will not be available in different pipelines, but is available to be downloaded from the UI.
The name artifacts sounds like it's only useful outside of the job, like for downloading a final image, but artifacts are also available in later stages within a pipeline.
另外,一樣key的cache會被覆蓋,而artifacts一旦生成就固定了,固然咱們能夠設置expire_in
,過時刪除之。
咱們定義一個最簡單的pipline:第一步編譯生成jar包,第二步將jar包導入docker鏡像並運行,在某些環節還需加入代碼review。由於最後咱們會以docker容器運行jar包,因此這裏不建議docker-based runner/executor的形式,由於該形式致使Docker-in-Docker
的場景,帶來可能的一些麻煩且難以解決的問題(好比內嵌容器如何關聯外部服務以及對外提供服務)。因此咱們直接宿主機安裝GitLab Runner。
若是必定要以docker-based形式,那麼可參看使用GitLab CI和Docker自動部署SpringBoot應用。在該文中,並無在runner所在宿主機中運行容器,而是將生成的鏡像發佈到鏡像倉庫,再登陸目標服務器拉取鏡像運行,因此不存在Docker-in-Docker的麻煩事。
# 1.Add the official GitLab repository curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh" | sudo bash # 2.Install the latest version of GitLab Runner export GITLAB_RUNNER_DISABLE_SKEL=true; sudo -E yum install gitlab-runner
註冊runner
sudo gitlab-runner register -n \ --url http://192.168.1.26:9980/ \ --registration-token cJMXGJWx7qx9AmpSc6ee \ --executor shell \ --tag-list "inkscreen_hostrunner" \ --description "Host Runner for InkScreen"
Add the gitlab-runner user to the docker group:
sudo usermod -aG docker gitlab-runner
stages: - build jar - build and run image #job's name 能夠隨意取 buildJar: stage: build jar variables: # 默認是clone,改成fetch加快拉取速度(若本地無則會自動clone) GIT_STRATEGY: fetch only: - dev script: - > docker run -d --rm --name justforpackage-$CI_COMMIT_REF_NAME -v "$(pwd)":/build/inkscreen -v /inkscreen/maven/m2:/root/.m2 -w /build/inkscreen maven:3-jdk-8 mvn clean package - sleep 60 tags: - inkscreen_hostrunner artifacts: paths: - louwen-admin/target/louwen-admin.jar expire_in: 3600 seconds testDeploy: stage: build and run image only: - dev variables: # 不拉取代碼 GIT_STRATEGY: none IMAGE_NAME: louwen/inkscreen-api:$CI_COMMIT_REF_NAME PORT: 38082 before_script: # 移除舊容器和鏡像。這裏爲何要寫成一行,下面有講 - if [ docker ps | grep inkscreen-$CI_COMMIT_REF_NAME ]; then docker stop inkscreen-$CI_COMMIT_REF_NAME; docker rm inkscreen-$CI_COMMIT_REF_NAME; docker rmi $IMAGE_NAME; fi script: - docker build --build-arg JAR_PATH=louwen-admin/target/louwen-admin.jar -t $IMAGE_NAME . - > docker run -d --name inkscreen-$CI_COMMIT_REF_NAME -p $PORT:$PORT --network my_bridge --env spring.redis.host=myredis -v /inkscreen/inkscreen-api/logs/:/logs/ -v /inkscreen/inkscreen-api/louwen-admin/src/main/resources/:/configs/ $IMAGE_NAME tags: - inkscreen_hostrunner
注意build jar環節咱們sleep了60秒,是由於docker run並不會等待內部腳本執行完,而是啓動後就直接返回了,此時jar包還沒有生成,因此此處阻塞一段時間等待打包結束。正常應該寫一段腳本循環判斷jar包是否已生成,若生成或超時則跳出循環,此處做爲演示簡單sleep。
在testDeploy任務中,before_script被我寫成了一行,最第一版本是:
before_script: # 若未找到記錄,則該條命令會返回1,gitlab就直接報錯返回了ERROR: Job failed: exit status 1 - docker ps | grep inkscreen-$CI_COMMIT_REF_NAME - > if [ $? -eq 0 ] then docker stop inkscreen-$CI_COMMIT_REF_NAME docker rm inkscreen-$CI_COMMIT_REF_NAME docker rmi $IMAGE_NAME fi
後改成
before_script: # 將檢測語句直接做爲條件內置,解決了上面的問題 - > if docker ps | grep inkscreen-$CI_COMMIT_REF_NAME then docker stop inkscreen-$CI_COMMIT_REF_NAME docker rm inkscreen-$CI_COMMIT_REF_NAME docker rmi $IMAGE_NAME fi
報錯syntax error near unexpected token 'fi'
,估計是換行/回車格式的緣由。上述兩個問題均可以經過單獨建.sh文件的方式解決,我這裏簡單地將全部語句排成一行。
生成鏡像天然少不了Dockerfile
FROM openjdk:8-jdk-oracle MAINTAINER louwen # 外部傳入,主程序路徑 ARG JAR_PATH ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 COPY $JAR_PATH /app.jar EXPOSE 38082 ENTRYPOINT ["java","-jar","/app.jar"]
題外話,其實咱們徹底能夠將build jar環節也放在Dockerfile中,以下
# # build jar stage # FROM maven:3-jdk-8 AS MAVEN_BUILD COPY pom.xml /build/ COPY src /build/src/ WORKDIR /build/ RUN mvn clean package # FROM openjdk:8-jdk-oracle MAINTAINER louwen COPY --from=MAVEN_BUILD /build/target/*.jar /app.jar ENTRYPOINT ["java","-jar","/app.jar"]
目前較流行的代碼檢測工具是SonarQube,不過其社區版本對同一個代碼倉庫沒法區分不一樣分支,從而實現按代碼的不一樣分支顯示對應分支的掃描結果。這裏咱們使用Gitlab-CI的Code Quality stage
,其使用的是Codeclimate
,它是爲代碼質量分析平臺提供的一個命令行接口工具,經過它能夠在本機 Docker 容器中對要分析的代碼執行質量分析,並生成分析報告。咱們熟知經常使用的代碼質量檢測工具例如 SonarQube、CheckStyle 等等,而 Codeclimate 接入了這些工具,並且支持咱們自定義檢測工具。
按照官方說法,使用Code Quality
須要基於docker-based runner/executor,因此咱們另外使用docker方式安裝GitLab-Runner並註冊一個runner(參考上述概念
小節),executor選擇docker。
include: - template: Code-Quality.gitlab-ci.yml # 如下配置參考網上一些資料,聽說是官方示例,然而我沒有在官方文檔找到 code_quality: image: docker:stable variables: DOCKER_DRIVER: overlay2 # gitlab 13.6及以後版本支持 REPORT_FORMAT: html allow_failure: true services: - docker:dind script: # 鏡像版本號格式參看 https://gitlab.com/gitlab-org/ci-cd/codequality/-/tree/master#versioning-and-release-cycle # - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/') - docker run --net=host --env SOURCE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock "registry.gitlab.com/gitlab-org/security-products/codequality:${VERSION:-latest}" /code artifacts: paths: [ gl-code-quality-report.html ] tags: - InkScreenAPI
執行的時候可能會卡在拉取鏡像環節。手動docker pull registry.gitlab.com/gitlab-org/security-products/codequality:latest
,發現各類超時。我開個阿里雲香港ECS的搶佔式實例(便宜)而後docker pull | save | load
將鏡像文件遷移到公司測試服,仍是報Unable to find image 'registry.gitlab.com/gitlab-org/security-products/codequality:latest' locally
,不知如何將host中的鏡像映射到docker:stable
中。看來仍是得kexue上網。
理論上,須要專人在合適的時候對提交的代碼進行質量把關,通常這工做能夠放在Merge Request
下進行。Merge Request的工做流程能夠參看在團隊中使用GitLab中的Merge Request工做模式
dial tcp: lookup docker on 192.168.1.1:53: no such host
錯誤。
This error occurs with docker-based gitlab runners such as the one we’re that are configured using a docker executor. The error message means that the inner docker container doesn’t have a connection to the host docker daemon.
解決:將/etc/gitlab-runner/config.toml
中對應的[runners.docker]節點設置privileged = true
,增長卷映射volumes = ["/cache", "/var/run/docker.sock:/var/run/docker.sock"]
或在.gitlab-ci.yml的job定義中增長services: - docker:dind
。
Cannot connect to the Docker daemon at tcp://docker:2375. Is the docker daemon running?
錯誤
解決:增長卷映射volumes = ["/certs/client", "/cache"]
,而後在.gitlab-ci.yml中增長變量DOCKER_TLS_CERTDIR: "/certs"
。
拉取代碼時提示warning: failed to remove xxxx: Permission denied
簡單粗暴地編輯/etc/passwd,將gitlab-runner帳號對應的uid:gid
改成0:0
(和root同樣)。
Code Quality提示docker: Error response from daemon: Head https://registry.gitlab.com/v2/gitlab-org/security-products/codequality/manifests/13-7-stable: Get https://gitlab.com/jwt/auth?scope=repository%3Agitlab-org%2Fsecurity-products%2Fcodequality%3Apull&service=container_registry: dial tcp [2606:4700:90:0:f22e:fbec:5bed:a9b9]:443: connect: cannot assign requested address.
在scripts->docker run增長參數--net=host
在pipline流程執行過程當中,咱們但願有任何風吹草動都能及時收到消息,郵件就是一個比較好的提醒方式。
vi /etc/gitlab/gitlab.rb
### GitLab email server settings ###! Docs: https://docs.gitlab.com/omnibus/settings/smtp.html ###! **Use smtp instead of sendmail/postfix.** gitlab_rails['smtp_enable'] = true gitlab_rails['smtp_address'] = "smtp.exmail.qq.com" gitlab_rails['smtp_port'] = 465 gitlab_rails['smtp_user_name'] = "xxxx@yyyy.com" gitlab_rails['smtp_password'] = "xxxxxxxx" gitlab_rails['smtp_domain'] = "exmail.qq.com" gitlab_rails['smtp_authentication'] = "login" gitlab_rails['smtp_enable_starttls_auto'] = true gitlab_rails['smtp_tls'] = true ### Email Settings gitlab_rails['gitlab_email_enabled'] = true ##! If your SMTP server does not like the default 'From: gitlab@gitlab.example.com' ##! can change the 'From' with this setting. ##! 要與上面的 smtp_user_name 保持一致 gitlab_rails['gitlab_email_from'] = 'xxxx@yyyy.com' # gitlab_rails['gitlab_email_display_name'] = 'Example' # gitlab_rails['gitlab_email_reply_to'] = 'noreply@example.com' # gitlab_rails['gitlab_email_subject_suffix'] = '' # gitlab_rails['gitlab_email_smime_enabled'] = false # gitlab_rails['gitlab_email_smime_key_file'] = '/etc/gitlab/ssl/gitlab_smime.key' # gitlab_rails['gitlab_email_smime_cert_file'] = '/etc/gitlab/ssl/gitlab_smime.crt' # gitlab_rails['gitlab_email_smime_ca_certs_file'] = '/etc/gitlab/ssl/gitlab_smime_cas.crt'
gitlab-ctl reconfigure
使配置生效
測試
gitlab-rails console irb(main):003:0> Notify.test_email('whatever@qq.com', 'Message Subject', 'Message Body').deliver_now
登陸whatever@qq.com查看受否收到信件。
jenkins + gitlab
若是使用jenkins做爲CI/CD工具,代碼由gitlab託管,那麼它們之間的交互須要兩個token:
mvn package、install、deploy都幹了什麼
由上可知:
alpine
Alpine Linux
是一個社區開發的面向安全應用的輕量級Linux發行版。不少鏡像都會專門基於Alpine構建,大小會小不少。好比:
修改GitLab-ce域名
剛部署好的GitLab新建的項目ssh地址通常是個短連接如git@AKDJF3ld:xxx,有時候會不太好使,能夠經過配置文件的修改,指向域名。
vim /opt/gitlab/embedded/service/gitlab-rails/config/gitlab.yml # host: 192.168.xx.xx # port: xxxx gitlab-ctl restart
當談到 GitLab CI 的時候,咱們該聊些什麼(上篇)
什麼是devops,基於Gitlab從零開始搭建本身的持續集成流水線(Pipeline)
持續集成之.gitlab-ci.yml篇
Building Docker images with GitLab CI/CD
自動化 DevOps 使用 Codeclimate 執行代碼質量分析
GitLab CI/CD
基於 Gitlab 的 Code Review 最佳實踐