大家好,我是坤哥
好久沒更了,最近幾周身體不好,得了比較嚴重的胃炎+心動過速症狀,跑了好幾趟醫院,嚴重的時候心臟感覺很不舒服,有瀕死感,胸悶氣短,有時幾乎整夜睡不好覺,在此奉勸大家還是要保重身體,千萬不要做熬夜等傷身體的傻事,千萬保重身體!
年前和年后我們完成了一次從 0 到 1 的上雲之旅,其中踩了不少坑,也積累了不少寶貴的經驗,所以在此總結成文,相信大家看了肯定有收獲
先說下此次上雲的背景,創業后,我們的業務是從集團中獨立出來了,不過系統還是和集團共用的,共用系統本來也不是個事兒,但由於集團早已今非昔比,核心人員都走得差不多了,導致一些核心系統不穩定,甚至出現過反向代理層宕機無人修復而導致整個交易跌零的嚴重事故,所以我們決定將系統完全從集團中剝離出來,由於之前集團系統上雲采用的是騰訊雲,所以我們也用了騰訊雲,這樣網絡可以做到互通,共用一些待遷移的系統的鏡像,也可以用騰訊雲的工具對數據進行增量/全量遷移等以降低遷移成本
雲服務都有哪些
現在上雲可以說是業界公認的趨勢了,因為像阿里雲,騰訊雲等雲廠商提供的工具真的太全了,基本上覆蓋了一個大廠系統所需的方方面面,不信?我們不妨一起來看看,先來看下大廠的基本系統架構:

可以看到一個完整的可運行的系統需要提供:DNS 解析,CDN 服務,接入層,中間件,存儲層,APM 等,雲上這些工具全有 ,而且基本都是一鍵部署,這里簡單舉兩個例子
以部署 ZK 集群為例,如果你要部署一個 ZK 集群,那一般要在三台虛擬機上部署(ZK集群要求至少提供三台服務器),還需要編輯配置文件等,涉及到這種人為的工作往往比較容易出錯,但在騰訊雲上點個按鈕就可以自動幫你生成一個 ZK 集群,你所要做的只需在工程中替換此 ZK 集群地址,同時還可以查看它的
基本信息
(部署架構),數據管理
,運行監控
(JVM,連接數,內存使用率等),運行日志
再比如你要部署一個 Redis 分片集群,一開始可能只需要一個分片,但后面隨着業務的發展,需要進行分片擴容,那就比較麻煩了,一般需要用官方提供的 redis-trib 管理軟件進行遷移,涉及到
創建新的節點
,將新的主節點加入集群
,轉移slot(重新分片)
,將從節點加入集群
這些步驟,很煩瑣,但如果用騰訊雲本身提供的工具,只要選擇對應的分片選項,再點確定,即可一鍵搞定(如下),非常方便,同樣的,騰訊雲也提供了 Redis 的緩存命中率
,慢查詢
,CPU 使用率
等監控
以上只是簡單舉了兩個例子,事實上,像 MySQL,ES,MQ 等組件騰訊雲上也基本都提供了一鍵式生成的操作,並且都附帶了相關的監控與告警機制,極大地降低了運維成本,使用這些工具唯一的缺點就是

但我們也有一些控制成本的手段 ,比如:
- 如果項目是內網的話(如運營中心等),完全可以把這些項目都部署在同一台低配的虛擬機上以節省成本
- 線上多個前端項目也可以同時部署在同一台機器上,配合 CDN 可以解決訪問過慢的問題
- 我們需要部署 APM(查看分布式調用鏈,JVM 監控等),就得在機器上布署 Skywalking 這樣的分布式追蹤框架以便采集數據,如果在每個服務的每台機器上都采集其實沒有必要,成本也比較高,所以我們后來調整為了每個服務只有一台機器進行采集,並且降低了采樣率,這樣上傳的數據就少很多,可以降低成本,采用這樣的方式之后, skywalking 的采集數據成本只需要每天 10 元左右
數據遷移
由於我們之前的系統是與集團共用的,現在要獨立成一套,那就必須搞獨立的 ZK,獨立的 Redis,獨立的 MQ,獨立的數據庫。。。,所有的這一切顯然需要做到數據的平滑遷移,具體的操作如下
數據庫遷移:使用騰訊雲上的數據遷移服務,進行全量+增量的升級,保證數據的一致性后再把數據全部切成我們的庫
配置中心數據遷移:之前集團使用的 ZK 作為配置中心,所以我們直接使用了一款開源好用的遷移工具 zkcopy,執行以下命令即可完成 ZK 的數據遷移
java -jar target/zkcopy.jar --source server:port/path --target server:port/path
Redis 遷移:另建一個 Redis 實例(只是 host 不同),使用 AOP 的形式讓原 Redis(集團 Redis)在寫入后新 Redis 也寫入,這樣維持一周左右,基本上就能把 Redis 的數據遷移完畢,偽代碼如下
@Slf4j
@Aspect
@Component
public class AopRedisReadWriteAspect {
@Resource
private RebateNewCacheClient rebateNewCacheClient; // 新 Redis 實例
@Around(value = "execution(* com.xxx.RedisCacheClient.setex(" +// RedisCacheClient 即集團 Redis 實例,使用 AOP 的方式可以讓集團的 Redis 寫入的同時也同步到我司的 Redis 實例來
// 從而最終實現數據一致性
"String,int,String)) && args(key,expire,value)")
public Object setEx(ProceedingJoinPoint joinPoint, String key, int expire, String value) {
rebateNewCacheClient.setEx(key, expire, value);
return invokeOrigin(joinPoint);
}
}private Object invokeOrigin(ProceedingJoinPoint joinPoint) {
try {
return joinPoint.proceed();
} catch (Throwable throwable) {
// 打印日志
}
return null;
}MQ 遷移:首先 MQ 的創建和 ZK 集群等創建一樣方便,我們要做的,只是確定好分區數等,確定好之后動動手指點擊一下,這樣就會一鍵生成一個高可用的 RocketMQ 集群,包括各種監控,消息狀態查閱等也應有盡有,如果你自己部署估計要搞半天,在遷移的時候,我們做了兩手准備,一是同時向集團和我司的 broker 發消息,然后灰度一部分用戶,這部分用戶只向我司的 broker 發消息,確認沒問題后,再停掉發集團的 broker 邏輯,這樣可以做到平穩過渡
Ansible 簡介
雖然雲上有很多服務可以幫助我們快速接入 Redis 等中間件,快速遷移 DB 等數據,但除了這些基本上雲服務可以提供的並不是說我們其他啥也不用干了,接下來我們來談下本文的重點:項目部署的架構設計。比如一個 Java 項目,你要跑起來,總得先編譯打包(生成 jar 包)吧,打包之后發布總不能立馬中斷正在運行的服務吧,你得用優雅停機的方式來停掉服務然后再部署新包,部署之后如果發現有問題要回滾吧,這些步驟如果用手工操作肯定不現實,最好的方式其實是寫成腳本的方式然后一鍵部署,不過一個服務可能有多台機器,難道我們需要一台台登錄然后再手動觸發相應的部署腳本?顯然不現實,所以我們需要一個批量部署的自動化運維工具,經過調研,我們選擇了 Ansible
什么是 Ansbile ,它有什么優勢?
Ansible
是一款簡單的運維自動化工具,只需要使用ssh
協議連接就可以實現批量系統配置
、批量程序部署
、批量運行命令
等功能

ansbile 有以下幾個優勢:
- 它是通過 SSH 來接管對應機器的控制權的,對應的機器無需安裝任何的 ansible 客戶端,也無需啟用額外的服務,所以即便 ansible 升級也不影響對應的機器
- 有大量常規運維操作模塊,可實現日常絕大部分操作
- 配置簡單、功能強大、擴展性強;
- 通過 Playbooks(劇本) 來定制強大的配置、狀態管理,所謂劇本,即 YAML 格式文件,多個任務定義在此文件中,定義主機需要用哪些模塊(主要有核心模塊和自定義模塊)來完成這些功能
由於它的上述這些特點,ansible 很快流行了起來,甚至可以說是運維必備的一款神器了,上圖是 ansible 的極簡版,我們再稍微展開一下它的架構看看

它的執行流程如下
用戶登錄(一般通過跳板機) ansible 所在機器
通過 Host Inventory 來指定要控制的主機,這個一般是個 yaml 文件,我們可以在此文件中指定所有我們可以控制的 host,另外一個服務通常有多台機器,我們也可以指定哪些機器屬於某個服務,這里簡單舉個例子
all:
hosts:
10.100.1.2:
10.100.1.4:
10.100.1.5:
children:
build:
hosts:
10.100.1.2:
operation_center:
hosts:
10.100.1.4:
10.100.1.5:上圖中,
10.100.1.4
,10.100.1.5
即屬於同一個服務operator_center
,如果我們到時要發布這個服務的話,只需要指定其服務名operator_center
即可(下文會介紹)通過步驟 2 我們即可指定需要操控哪些機器,然后 ansible 再通過連接模塊(即 Connection Plugins,采用 SSH 連接)連接步驟 2 中指定的 host 然后利用核心模塊(core modules)等來執行我們寫好的 Playbooks(劇本)
ansible 的 core modules(核心模塊)有很多,功能也很強大,基本不需要自定義模塊,像我們這次上雲也只用了核心模塊,來看幾個比較常見的模塊
shell模塊
:可以在遠程主機上調用 shell 解釋器運行命令,支持 shell 的各種功能,例如管道等。copy 模塊
:將文件復制到遠程主機,同時支持給定內容生成文件和修改權限等file 模塊
:設置文件的屬性,比如創建文件、創建鏈接文件、刪除文件等fetch模塊
:從遠程某主機獲取(復制)文件到本地(即 ansible 所在機器)command 模塊
:在遠程主機上執行命令,並將結果返回到調用機上(也就是 ansible 所在主機)cron 模塊
:定時任務模塊,這個大家應該比較熟悉了
我們知道一般工程都需要構建(或者說打包,兩個概念相差不大)之后才能部署,比如 Java 工程要打包成 Jar 文件然后再部署(執行 Jar 包),前端工程也需要打包后才能部署(比如把多個 js 文件合並成一個以減少請求提升性能,再比如你可能使用 SCSS 或 less 來寫 CSS,也需要編譯成 CSS 文件,並且合並起來),那么問題來了,能否直接在生產機器上執行打包操作呢?答案顯然是否定的,主要有兩個原因
- 打包由於采用了各種優化手段(比如並行打包等)是很耗費 CPU 的,如果在生產上正在對外服務的機器上執行打包操作的話,那么很可能由於打包時耗費的 CPU 過大而導致當前正在執行的機器出現響應太慢,拒絕請求等問題,這顯然是不可接受的
- 服務是以集群的形式存在的,可能一個服務有好幾台機器,這些機器部署其實所需的 jar 包完全是一樣的,沒有必要在各個機器上都執行一遍通過的打包操作
部署架構設計
綜上,我們需要一個專門的打包機,將打包的工作交給打包機,打包機打包好之后,我們再把相應的包發到生產的機器上,然后再執行部署腳本,架構模型如下

通過這樣的方式,打包機承擔了所有繁重的活,打包之后,ansible 會通過 fetch 模塊將這些 jar 包拉到本地,然后再通過 push 模塊把 jar 包 push 到服務集群上的所有機器,然后再執行比較輕量級的部署腳本
提到上文中的諸多模塊,大家可以沒有什么概念,那么接下來我們一起來看下 ansible 如何利用上文所述的幾個核心模塊來完成我們所設計的這個打包部署步驟,這樣大家對 ansible 的功能也能有更全面的認識
樣例腳本我們一一介紹下:有三個文件
production-hosts.yaml 文件:即我們上文提到的的 host inventory
#production-hosts.yaml
all:
hosts:
10.100.1.2:
10.100.1.4:
10.100.1.5:
children:
build: # 打包機
hosts:
10.100.1.2:
operation_center: # operation_center 服務
hosts:
10.100.1.4:
10.100.1.5:打包 playbook: java-build.yaml
# java-build.yaml
- name: build project artifact
hosts: build # build 表示打包機,定義在 production-hosts.yaml 文件中
tasks:
- name: Pull source code and checkout branch
ansible.builtin.git:
repo: '工程git地址'
dest: workspace/operation_center
version: master
force: yes
- name: Grant execute permission to build.sh
ansible.builtin.file: # file 模塊
path: workspace/operation_center/build.sh
mode: '0755'
- name: Build project # 執行打包腳本
ansible.builtin.shell: ./build.sh # shell 模塊
args:
chdir: workspace/operation_center
executable: /bin/bash
- name: Create archive project # 打包后會生成 jar 包,創建目錄存放存放即將壓縮后的 jar 包
ansible.builtin.file:
path: archive/operation_center
state: directory
args:
chdir: /home/buser
- name: Archive latest artifact # 壓縮 jar 包到上一步創建的目錄中
ansible.builtin.shell: cp workspace/operation_center/target/operation_center.jar archive/operation_center/latest.jar
args:
chdir: /home/buser
- name: Fetch project artifact to local # 使用 fetch 模塊將上一步壓縮的 jar 包從打包機拉到 ansible 所在機器上
ansible.builtin.fetch:
src: archive/operation_center/latest.jar
dest: /tmp/operation_center/
flat: yes
- name: Fetch bin file to local # 將部署腳本從打包機拉到本地,准備傳給線上機器執行部署操作
ansible.builtin.fetch:
src: /home/buser/workspace/operation_center/deploy.sh
dest: /tmp/operation_center/
flat: yes
- 部署 playbook: java-deploy.yaml
# java-deploy.yaml
- name: Deploy project
hosts: operation_center # 表示線上服務器,定義在 production-hosts.yaml 文件中
serial: 1
any_errors_fatal: true # 只要一步失敗,部署流程即終止
tasks:
- name: Upload artifact & restart project
block:
- name: Push project artifact to remote # 將 ansbile 上的 jar 包 push 到服務器上
ansible.builtin.copy:
src: /tmp/operation_center/latest.jar
dest: /opt/apps/business/operation_center.jar
- name: Push deploy file to remote # 將 ansbile 上的部署腳本 push 到服務器上
ansible.builtin.copy:
src: /tmp/operation_center/deploy.sh
dest: /opt/apps/bin/
- name: Start operation_center
ansible.builtin.shell: bash bin/start.sh # 執行部署腳本
args:
chdir: /opt/apps
executable: /bin/bash
environment:
appEnv: prod
- name: Health check 8001 # 健康檢查
uri:
url: http://127.0.0.1:8001/service/health/deepCheck
return_content: yes
status_code: -1
register: this
failed_when: "'health' not in this.content"
when: health_check == "8001"
- name: Health check 8081
uri:
url: http://127.0.0.1:8081/health/check
return_content: yes
register: this
failed_when: "'health' not in this.content"
when: health_check == "8081"
become: yes # 在線上服務器上以 root 身份執行上述的部署步驟
become_user: root
有了以上三個文件,只要分別執行打包和部署操作 playbook 即可,如下
ansible-playbook -i production-hosts.yaml java-build.yaml # 打包
ansible-playbook -i production-hosts.yaml java-deploy.yaml # 部署
可以看到只要使用 ansible 的核心模塊即可完成我們的打包部署需求,執行流程會通過上文中的 name 展示,部署流程部分展示如下

可以看到整個流程部署流程非常的清晰!
為了方便起見,以上腳本只是簡單介紹了一下打包部署的部分步驟,其實我們還需考慮回滾等操作,由於不是本文的重點,所以這里就不再做介紹了
聊點題外話
現在各個雲廠商提供的工具確實相當廣泛,而且很好用了,基本每個開發都能承擔 devops(開發運維) 的工作,於是就有了一個擔憂:開發的工作到時會不會被替換,尤其是運維就更有這樣的憂慮了,之前就有一位運維哥們說這些雲廠商工具這么好用,很擔心自己的飯碗被搶了,也確實,畢竟基本上你能想到的運維工作它都能一鍵點擊幫你做了,甚至"侵蝕"到數據庫,中間件這些領域,曾經我看到過一個數據庫團隊的 Leader 反對上雲,說了一大堆上雲的缺點,但他自己私下其實也說了,上雲的趨勢已經不可阻擋了,很擔心有一天會失業 。
之前就有一位讀者提到下面這樣的問題

我的回答是像這些其實雲廠商都提供了很成熟的解決方案,基本一鍵生成,於是就有了這位讀者的靈魂拷問

我相信很多人也有這有這樣的疑問,我的看法是雲服務廠商這些提供的只是工具,但如何利用好這些工具,還需要我們掌握比較扎實的理論,所以我給的建議如下

開發如果想向更高階發展,還是要掌握扎實的理論基礎,這就好比,給你一把屠龍刀,沒有深厚的內功你能揮得動,揮得好嗎