設想一下這些場景:Nuxt 版本出現重大更新須要升級;新功能的開發須要添加新的生產環境依賴;線上版本出現 bug 急需快速回滾至上一版本;開發環境和生產環境依賴版本一致性的保持……java
咱們須要解決的不只僅是不停機更新,還要使開發環境和生產環境的版本保持強一致性,而且能夠輕鬆地追溯歷史版本,以及更新過程使用戶無感知等等。綜合以上特質,能夠選擇 docker 一試。node
Docker 是一個開源的應用容器引擎,基於 Go 語言並聽從 Apache2.0 協議開源。linux
它號稱本身是實如今任何地方安全構建、分享和運行現代應用的最快實現方式。它可讓咱們把應用及其依賴所有打包到一個容器中,從而輕鬆地實如今任何系統下極速遷移。就像其 logo 展現的那樣,一艘鯨魚樣子的大船載滿集裝箱飄蕩在海面上。各個 OS 就是海洋,docker 承載着一個個容器飄蕩在 OS 的海洋裏。nginx
以 Nuxt 應用爲例,若有須要,咱們能夠將其運行須要依賴的 Nginx 、 NodeJs 、應用自己及其依賴等通通打包到一塊兒。此時就算你拿到一個新的服務器,只要上面安裝了 docker,只須要幾個簡單的指令就可讓應用運行起來,而不須要再進行繁瑣地配置。git
具體的使用細節再也不絮叨了,茫茫多的 docker 官方文檔 正向你招手~菜鳥教程和 docker 中文社區也是系統學習的不錯選擇。web
慣例,拋出 docker cli 的經常使用指令:docker
# 基於當前文件夾下的 dockerfile 建立一個鏡像
docker build -t helloworld .
# 上面指令的全寫
docker build --tag=helloworld .
# 運行這個鏡像,並將本機 4000 端口映射到容器對外暴露的 80 端口,外部經過 4000端口訪問
docker run -p 4000:80 helloworld # 使 container 在後臺運行
docker run -d -p 4000:80 helloworld # 全部正在運行的容器列表
docker container ls
# 全部容器列表
docker container ls -a
# 停用一個容器
docker container stop <hash>
# 強制中止一個容器
docker container kill <hash>
# 移除一個容器
docker container rm <hash>
# 移除全部容器
docker container rm $(docker container ls -a -q)
# 全部鏡像列表
docker image ls -a
# 移除一個鏡像
docker image rm <image id>
# 移除全部鏡像
docker image rm $(docker image ls -a -q)
# 登陸註冊過的 docker hub
docker login
# 爲鏡像打標籤
docker tag <image> username/repository:tag
# 將鏡像推送至遠程倉庫
docker push username/repository:tag
# 運行這個鏡像
docker run username/repository:tag
複製代碼
此時,容器 (container) 即當前承載應用的進程,它擁有一個獨立的文件系統,裏面包含了應用所需的全部代碼、依賴和運行時等等。這被稱之爲鏡像 (image) 的存在,正是咱們須要在以後生產並保存起來的東西。shell
假如每一次版本發佈咱們都生成一個鏡像 (image),並基於項目和版本號爲其打上獨一無二的標籤,而後把它們保存到一塊兒,須要的時候隨時取用,依賴更新的問題天然而然獲得解決。而當須要發佈新版本的時候,咱們只須要提早拉新版本的鏡像到生產環境,而後移除舊版本鏡像正在運行的容器,同時執行新版本的容器,開啓新的 container,這樣就能夠實現無縫升級,也實現了某種意義上的不停機。npm
接下來,咱們要有一個存儲版本鏡像的倉庫。docker 本身推出了一個鏡像組織和託管平臺,docker hub,學習的時候能夠簡單地用它來熟悉整個操做流程。以後能夠搭建本身的私有倉庫來知足實際的業務需求。由於咱們實際業務中各類服務器都基於阿里雲產品,因此這裏也選擇阿里雲容器鏡像服務來管理鏡像。json
倉庫的問題解決了,咱們又但願在每次 push 一個 tag 的時候可以自動地生成一個鏡像並上傳至倉庫。咱們的項目代碼託管在私有 GitLab 上,它自己就集成了不錯的 CI/CD 功能,能夠做爲備選的目標之一。咱們這裏選擇使用 Jenkins (一個基於 java 的持續集成工具)並結合 GitLab 的 webhooks 來實現這一需求。
最終方案定爲 GitLab + Jenkins + Docker + 阿里雲鏡像服務,下面來看一下大致的實施步驟。
安裝 java -> yum install java
添加 Jenkins 庫至 yum -> sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
導入公鑰 -> sudo rpm --import https://jenkins-ci.org/redhat/jenkins-ci.org.key
安裝 -> sudo yum -y install jenkins
查看 Jenkins 根目錄 -> cd /var/lib/jenkins
配置 Jenkins 根權限:
vim /etc/sysconfig/jenkins
,修改或添加項 ->JENKINS_USER="root"
和JENKINS_GROUP="root"
,保存退出 執行gpasswd -a root jenkins
這裏jenkins服務的默認端口爲 8080,可在配置文件中修改,保存後執行service Jenkins restart
重啓服務
啓動Jenkins -> service jenkins start
。此時訪問http://<服務器公網ip>:8080
開啓 Jenkins 界面
在 /var/lib/jenkins/secrets/initalAdminPassword
下獲取管理員密碼以開啓 Jenkins
設置管理員帳號密碼後,便可開始使用 Jenkins
Docker 社區版和企業版,這裏選擇安裝社區版本
$ sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest- logrotate docker-logrotate docker-engine
複製代碼
$ sudo yum install -y yum-utils device-mapper-persistent-data lvm2
$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
複製代碼
$ sudo yum install docker-ce docker-ce-cli containerd.io
$ sudo systemctl start docker
$ sudo docker run hello-world
nuxt-demo
爲例的 Dockerfile 配置文件,因爲 docker 自身擁有守護進程和健康檢查的功能,因此能夠考慮放棄 pm2:FROM node:10.16.0
ENV HOST 0.0.0.0
RUN mkdir -p /app COPY . /app WORKDIR /app EXPOSE 3000
RUN npm install RUN npm run build CMD ["npm", "start"]
複製代碼
訪問阿里雲容器鏡像服務
登陸後,建立命名空間,建立鏡像倉庫,這裏以 psl_one
爲例
因爲這裏經過 jenkins 來構建 docker 鏡像,因此再也不配置代碼源,選擇本地倉庫並建立 (阿里雲鏡像服務自己也提供了鏡像構建功能)
進入新建立的鏡像倉庫,能夠在鏡像版本中查看已上傳的鏡像列表,在基本信息中查看該倉庫的基本信息,其第三條說明將鏡像推送到Registry中有接下來要用到的指令:
$ sudo docker login --username=[username] registry.cn-qingdao.aliyuncs.com
$ sudo docker tag [ImageId] registry.cn-qingdao.aliyuncs.com/[命名空間]/hjxy_test:[鏡像版本號]
$ sudo docker push registry.cn-qingdao.aliyuncs.com/[命名空間]/hjxy_test:[鏡像版本號]
複製代碼
首先安裝一些必要的插件。進入系統管理->插件管理->可選插件,在過濾中搜索須要的插件
http://mirror.xmission.com/jenkins/updates/update-center.json
Localization: Chinese (Simplified)
簡體中文語言包SSH Plugin
經過ssh鏈接遠程服務器執行shell腳本Git Parameter Plug-In
在參數化構建過程選項下得到git相關參數,如branch/tag等Generic Webhook Trigger Plugin
構建觸發器插件,接收一個http請求,與webhook配合,觸發job執行構建nvm-wrapper
提供一個nodejs的構建環境Email Extension Plugin
設置郵件通知的插件進入系統管理 -> 系統設置,進行 ssh 配置
進入系統管理 -> 管理用戶,查看|生成用戶 id 和用戶 token
進入系統管理 -> 系統設置,配置郵件通知,這裏以 qq 郵箱爲例
點擊新建任務,選擇建立一個自由風格的項目,爲 nuxt_demo 建立一個鏡像構建、上傳並部署的 jenkins job。約定這個任務只用於當開發者向倉庫提交 tag 時觸發構建,不能進行主動構建(就算主動構建,由於缺乏 tag 信息也會失敗)
Generic Webhook Trigger
因爲 GitLab 的 webhook 傳遞來的 ref 信息形如
refs/tags/v1.0.0
,因此須要在執行腳本中進行字符串拆分,獲取須要的部分:$ref|cut -c11-
Execute shell script on remote host using ssh
ssh site
即剛纔在系統設置中配置的ssh ,pre build script
能夠置空或根據須要填寫,這裏主要配置post build script
,其中***-deploy.sh
爲遠程服務器中的部署腳本,將在構建任務結束後觸發執行
這裏只選擇tag push events
,點擊add webhook
添加鉤子,並點擊 test按鈕測試是否可用 若成功,將觸發相應的 jenkins job 執行
/var/myprojects/shell_script/***-deploy.sh
#!/bin/bash
echo "deploy start"
preImageId=$(docker inspect -f {{.Image}} <Container Name>) # 獲取當前運行容器的鏡像Id,稍後用以刪除鏡像
docker login -u <username> -p <password> <Docker Registry> # 登陸遠程鏡像倉庫
docker <Image>:$1 # 拉取最新構建的鏡像
docker rm -f <Container Name> || true # 強制刪除當前運行的容器
docker run -d --name <Container Name> -p 3000:3001 --restart=always <Image>:$1 # 使用最新拉取的鏡像開啓新的容器
docker rm -f <Container Name2> || true # 強制刪除當前運行的容器
docker run -d --name <Container Name2> -p 3001:3001 --restart=always <Image>:$1 # 使用最新拉取的鏡像開啓新的容器
docker image rm -f ${preImageId} # 移除上一個版本的鏡像
echo "deploy end"
複製代碼
upstream nuxt_demo {
server localhost:3000 max_fails=1 fail_timeout=15s weight=1;
server localhost:3001 max_fails=1 fail_timeout=15s weight=1;
}
server {
listen 80;
server_name test.xyz.docker.com;
location / {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Nginx-Proxy true;
proxy_cache_bypass $http_upgrade;
proxy_pass http://nuxt_demo;
}
}
複製代碼
參考第六節中的部分,但再也不配置構建步驟。 在general選項卡中勾選參數化構建過程選項,並配置以下參數:
此時,側邊欄的 當即構建將變成build with parameters
點擊
build with parameters
將看到以下界面。releasetag 默認值爲配置時填寫的 notag,此時可參照 taglist 填寫正確的值執行構建操做:
最終的任務列表大體以下:
以上,提供一種可行思路的大體操做步驟,如要在生產環境實行還需細細考慮具體的場景和更多的細節問題~