你管這破玩意兒叫上云?
大家好,我是坤哥。
好久沒更了,最近幾周身體不好,得了比較嚴重的胃炎+心動過速癥狀,跑了好幾趟醫(yī)院,嚴重的時候心臟感覺很不舒服,胸悶氣短,有瀕死感,有時幾乎整夜睡不好覺,在此奉勸大家還是要保重身體,千萬不要做熬夜等傷身體的傻事,千萬保重身體!
年前和年后我們完成了一次從 0 到 1 的上云之旅,其中踩了不少坑,也積累了不少寶貴的經(jīng)驗,所以在此總結(jié)成文,相信大家看了肯定有收獲。
先說下此次上云的背景,創(chuàng)業(yè)后,我們的業(yè)務(wù)是從集團中獨立出來了,不過系統(tǒng)還是和集團共用的,共用系統(tǒng)本來也不是個事兒,但由于集團早已今非昔比,核心人員都走得差不多了,導(dǎo)致一些核心系統(tǒng)不穩(wěn)定,甚至出現(xiàn)過反向代理層宕機無人修復(fù)而導(dǎo)致整個交易跌零的嚴重事故,所以我們決定將系統(tǒng)完全從集團中剝離出來,由于之前集團系統(tǒng)上云采用的是騰訊云,所以我們也用了騰訊云,這樣網(wǎng)絡(luò)可以做到互通,共用一些待遷移的系統(tǒng)的鏡像,也可以用騰訊云的工具對數(shù)據(jù)進行增量/全量遷移等以降低遷移成本。
云服務(wù)都有哪些
現(xiàn)在上云可以說是業(yè)界公認的趨勢了,因為像阿里云,騰訊云等云廠商提供的工具真的太全了,基本上覆蓋了一個大廠系統(tǒng)所需的方方面面,不信?我們不妨一起來看看,先來看下大廠的基本系統(tǒng)架構(gòu):
可以看到一個完整的可運行的系統(tǒng)需要提供:DNS 解析,CDN 服務(wù),接入層,中間件,存儲層,APM 等,云上這些工具全有 ,而且基本都是一鍵部署,這里簡單舉兩個例子:
- 以部署 ZK 集群為例,如果你要部署一個 ZK 集群,那一般要在三臺虛擬機上部署(ZK集群要求至少提供三臺服務(wù)器),還需要編輯配置文件等,涉及到這種人為的工作往往比較容易繁瑣,而且容易出錯,但在騰訊云上點個按鈕就可以自動幫你生成一個 ZK 集群,你所要做的只需在工程中替換此 ZK 集群地址,同時還可以查看它的基本信息(部署架構(gòu)),數(shù)據(jù)管理,運行監(jiān)控(JVM,連接數(shù),內(nèi)存使用率等),運行日志。
- 再比如你要部署一個 Redis 分片集群,一開始可能只需要一個分片,但后面隨著業(yè)務(wù)的發(fā)展,需要進行分片擴容,那就比較麻煩了,一般需要用官方提供的 redis-trib 管理軟件進行遷移,涉及到創(chuàng)建新的節(jié)點,將新的主節(jié)點加入集群,轉(zhuǎn)移slot(重新分片),將從節(jié)點加入集群這些步驟,很煩瑣,但如果用騰訊云本身提供的工具,只要選擇對應(yīng)的分片選項,再點確定,即可一鍵搞定(如下),非常方便,同樣的,騰訊云也提供了 Redis 的緩存命中率,慢查詢,CPU 使用率等監(jiān)控。
以上只是簡單舉了兩個例子,事實上,像 MySQL,ES,MQ 等組件騰訊云上也基本都提供了一鍵式生成的操作,并且都附帶了相關(guān)的監(jiān)控與告警機制,極大地降低了運維成本,使用這些工具唯一的缺點就是:
但我們也有一些控制成本的手段 ,比如:
- 如果項目是內(nèi)網(wǎng)的話(如運營中心等),完全可以把這些項目都部署在同一臺低配的虛擬機上以節(jié)省成本。
- 線上多個前端項目也可以同時部署在同一臺機器上,配合 CDN 可以解決訪問過慢的問題。
- 我們需要部署 APM(查看分布式調(diào)用鏈,JVM 監(jiān)控等),就得在機器上部署 Skywalking 這樣的分布式追蹤框架以便采集數(shù)據(jù),如果在每個服務(wù)的每臺機器上都采集其實沒有必要,成本也比較高,所以我們后來調(diào)整為了每個服務(wù)只有一臺機器進行采集,并且降低了采樣率,這樣上傳的數(shù)據(jù)就少很多,可以降低成本,采用這樣的方式之后, skywalking 的采集數(shù)據(jù)成本只需要每天 10 元左右。
數(shù)據(jù)遷移
由于我們之前的系統(tǒng)是與集團共用的,現(xiàn)在要獨立成一套,那就必須搞獨立的配置中心,獨立的 Redis,獨立的 MQ,獨立的數(shù) DB......,所有的這一切都不是一蹴而就的,顯然需要做到數(shù)據(jù)的平滑遷移,具體的操作如下:
- 數(shù)據(jù)庫遷移:使用騰訊云上的數(shù)據(jù)遷移服務(wù),進行全量+增量的升級,保證數(shù)據(jù)的一致性后再把數(shù)據(jù)源全部切成我們的庫。
- 配置中心數(shù)據(jù)遷移:之前集團使用的 ZK 作為配置中心,所以我們直接使用了一款開源好用的遷移工具 zkcopy,執(zhí)行以下命令即可完成 ZK 的數(shù)據(jù)遷移。
java -jar target/zkcopy.jar --source server:port/path --target server:port/path
Redis 遷移:另建一個 Redis 實例(只是 host 不同),使用 AOP 的形式讓原 Redis(集團 Redis)在寫入后新 Redis 也一起寫入,這樣維持一周左右,基本上就能把 Redis 的數(shù)據(jù)遷移完畢,偽代碼如下:
@Slf4j
@Aspect
@Component
public class AopRedisReadWriteAspect {
@Resource
private RebateNewCacheClient rebateNewCacheClient; // 新 Redis 實例
// RedisCacheClient 即集團 Redis 實例,使用 AOP 的方式可以讓集團的 Redis 寫入的同時也同步到我司的 Redis 實例來
// 從而最終實現(xiàn)數(shù)據(jù)一致性
@Around(value = "execution(* com.xxx.RedisCacheClient.setex(" +
"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 的創(chuàng)建和 ZK 集群等創(chuàng)建一樣方便,我們要做的,只是確定好分區(qū)數(shù)等,確定好之后動動手指點擊一下,這樣就會一鍵生成一個高可用的 RocketMQ 集群,包括各種監(jiān)控,消息狀態(tài)查閱等也應(yīng)有盡有,如果你自己部署估計要搞半天,在遷移的時候,我們做了兩手準(zhǔn)備,一是同時向集團和我司的 broker 發(fā)消息,然后灰度一部分用戶,這部分用戶只向我司的 broker 發(fā)消息,確認沒問題后,再停掉發(fā)集團的 broker 邏輯,這樣可以做到平穩(wěn)過渡。
Ansible 簡介
雖然云上有很多服務(wù)可以幫助我們快速接入 Redis 等中間件,快速遷移 DB 等數(shù)據(jù),但除了這些基本上云服務(wù)可以提供的并不是說我們其他啥也不用干了,接下來我們來談下本文的重點:項目部署的架構(gòu)設(shè)計。比如一個 Java 項目,你要跑起來,總得先編譯打包(生成 jar 包)吧,打包之后發(fā)布總不能立馬中斷正在運行的服務(wù)吧,你得用優(yōu)雅停機的方式來停掉服務(wù)然后再部署新包,部署之后如果發(fā)現(xiàn)有問題要回滾吧,這些步驟如果用手工操作肯定不現(xiàn)實,最好的方式其實是寫成腳本的方式然后一鍵部署,不過一個服務(wù)可能有多臺機器,難道我們需要一臺臺登錄然后再手動觸發(fā)相應(yīng)的部署腳本?顯然不現(xiàn)實,更合理的方式是首先要把所有的這些部署步驟寫成腳本的形式,然后再用一個支持批量部署的自動化運維工具來部署,經(jīng)過調(diào)研,我們選擇了 Ansible。
什么是 Ansbile ,它有什么優(yōu)勢?
Ansible是一款簡單的運維自動化工具,只需要使用ssh協(xié)議連接就可以實現(xiàn)批量系統(tǒng)配置、批量程序部署、批量運行命令等功能。
ansbile 有以下幾個優(yōu)勢:
- 它是通過 SSH 來接管對應(yīng)機器的控制權(quán)的,對應(yīng)的機器無需安裝任何的 ansible 客戶端,也無需啟用額外的服務(wù),所以即便 ansible 升級也不影響對應(yīng)的機器;
- 有大量常規(guī)運維操作模塊,可實現(xiàn)日常絕大部分操作;
- 配置簡單、功能強大、擴展性強;
- 通過 Playbooks(劇本) 來定制強大的配置、狀態(tài)管理,所謂劇本,即 YAML 格式文件,多個任務(wù)定義在此文件中,定義主機需要用哪些模塊(主要有核心模塊和自定義模塊)來完成這些功能。
由于它的上述這些特點,Ansible 很快流行了起來,甚至可以說是運維必備的一款神器了,上圖是 Ansible 的極簡版,我們再稍微展開一下它的架構(gòu)看看:
它的執(zhí)行流程如下:
- 用戶登錄(一般通過跳板機) ansible 所在機器。
- 通過 Host Inventory 來指定要控制的主機,這個一般是個 yaml 文件,我們可以在此文件中指定所有我們可以控制的 host,另外一個服務(wù)通常有多臺機器,我們也可以指定哪些機器屬于某個服務(wù),這里簡單舉個例子:
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即屬于同一個服務(wù)operator_center,如果我們到時要發(fā)布這個服務(wù)的話,只需要指定其服務(wù)名 operator_center 即可(下文會介紹)。
- 通過步驟 2 我們即可指定需要操控哪些機器,然后 ansible 再通過連接模塊(即 Connection Plugins,采用 SSH 連接)連接步驟 2 中指定的 host 然后利用核心模塊(core modules)寫好相應(yīng)的 Playbooks 并執(zhí)行。
ansible 的 core modules(核心模塊)有很多,功能也很強大,基本不需要自定義模塊,像我們這次上云也只用了核心模塊,來看幾個比較常見的模塊:
- shell模塊:可以在遠程主機上調(diào)用 shell 解釋器運行命令,支持 shell 的各種功能,例如管道等。
- copy 模塊:將文件復(fù)制到遠程主機,同時支持給定內(nèi)容生成文件和修改權(quán)限等。
- file 模塊:設(shè)置文件的屬性,比如創(chuàng)建文件、創(chuàng)建鏈接文件、刪除文件等。
- fetch模塊:從遠程某主機獲取(復(fù)制)文件到本地(即 ansible 所在機器)。
- command 模塊:在遠程主機上執(zhí)行命令,并將結(jié)果返回到調(diào)用機上(也就是 ansible 所在主機)。
- cron 模塊:定時任務(wù)模塊,這個大家應(yīng)該比較熟悉了。
我們知道一般工程都需要構(gòu)建(或者說打包,兩個概念相差不大)之后才能部署,比如 Java 工程要打包成 Jar 文件然后再部署(執(zhí)行 Jar 包),前端工程也需要打包后才能部署(比如把多個 js 文件合并成一個以減少請求提升性能,再比如你可能使用 SCSS 或 less 來寫 CSS,也需要編譯成 CSS 文件,并且合并起來),那么問題來了,能否直接在生產(chǎn)機器上執(zhí)行打包操作呢?答案顯然是否定的,主要有兩個原因:
- 打包由于采用了各種優(yōu)化手段(比如并行打包等)是很耗費 CPU 的,如果在生產(chǎn)上正在對外服務(wù)的機器上執(zhí)行打包操作的話,那么很可能由于打包時耗費的 CPU 過大而導(dǎo)致當(dāng)前正在執(zhí)行的機器出現(xiàn)響應(yīng)太慢,拒絕請求等問題,這顯然是不可接受的。
- 服務(wù)是以集群的形式存在的,可能一個服務(wù)有好幾臺機器,這些機器部署其實所需的 jar 包完全是一樣的,沒有必要在各個機器上都執(zhí)行一遍通過的打包操作。
部署架構(gòu)設(shè)計
綜上,我們需要一個專門的打包機,將打包的工作交給打包機,打包機打包好之后,我們再把相應(yīng)的包發(fā)到生產(chǎn)的機器上,然后再執(zhí)行部署腳本,架構(gòu)模型如下:
通過這樣的方式,打包機承擔(dān)了所有繁重的活,打包之后,ansible 會通過 fetch 模塊將這些 jar 包拉到本地,然后再通過 push 模塊把 jar 包 push 到服務(wù)集群上的所有機器,然后再執(zhí)行比較輕量級的部署腳本。
介紹了這么多 Ansible 相關(guān)的概念,大家可能還是一臉懵逼,那么接下來我們一起來看下如何利用 Ansible 來執(zhí)行我們所設(shè)計的打包部署步驟,這樣大家對 Ansible 的功能也能有更全面的認識。
樣例腳本我們一一介紹下:有三個文件
1.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 服務(wù)
hosts:
10.100.1.4:
10.100.1.5:
2.打包 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 // 執(zhí)行打包腳本
ansible.builtin.shell: ./build.sh // shell 模塊
args:
chdir: workspace/operation_center
executable: /bin/bash
- name: Create archive project // 打包后會生成 jar 包,創(chuàng)建目錄存放存放即將壓縮后的 jar 包
ansible.builtin.file:
path: archive/operation_center
state: directory
args:
chdir: /home/buser
- name: Archive latest artifact // 壓縮 jar 包到上一步創(chuàng)建的目錄中
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 // 將部署腳本從打包機拉到本地,準(zhǔn)備傳給線上機器執(zhí)行部署操作
ansible.builtin.fetch:
src: /home/buser/workspace/operation_center/deploy.sh
dest: /tmp/operation_center/
flat: yes
3.部署 playbook: java-deploy.yaml;
# java-deploy.yaml
- name: Deploy project
hosts: operation_center # 表示線上服務(wù)器,定義在 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 到服務(wù)器上
ansible.builtin.copy:
src: /tmp/operation_center/latest.jar
dest: /opt/apps/business/operation_center.jar
- name: Push deploy file to remote # 將 ansbile 上的部署腳本 push 到服務(wù)器上
ansible.builtin.copy:
src: /tmp/operation_center/deploy.sh
dest: /opt/apps/bin/
- name: Start operation_center
ansible.builtin.shell: bash bin/start.sh # 執(zhí)行部署腳本
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 # 在線上服務(wù)器上以 root 身份執(zhí)行上述的部署步驟
become_user: root
有了以上三個文件,只要分別執(zhí)行打包和部署操作 playbook 即可,如下:
ansible-playbook -i production-hosts.yaml java-build.yaml # 在打包機中打包
ansible-playbook -i production-hosts.yaml java-deploy.yaml # 在線上服務(wù)器上部署
可以看到只要使用 ansible 的核心模塊即可完成我們的打包部署需求,執(zhí)行流程會通過上文中的 name 展示,部署流程部分展示如下:
可以看到整個流程部署流程非常的清晰!
為了方便起見,以上腳本只是簡單介紹了一下打包部署的部分步驟,其實我們還需考慮回滾等操作,由于不是本文的重點,所以這里就不再做介紹了。
聊點題外話
現(xiàn)在各個云廠商提供的工具確實相當(dāng)廣泛,而且很好用了,基本每個開發(fā)都能承擔(dān) devops(開發(fā)運維) 的工作,于是就有了一個擔(dān)憂:開發(fā)的工作到時會不會被替換,尤其是運維就更有這樣的憂慮了,之前就有一位運維哥們說這些云廠商工具這么好用,很擔(dān)心自己的飯碗被搶了,也確實,畢竟基本上你能想到的運維工作它都能一鍵點擊幫你做了,甚至"侵蝕"到數(shù)據(jù)庫,中間件這些領(lǐng)域,曾經(jīng)一個數(shù)據(jù)庫團隊的 Leader 反對上云,說了一大堆上云的缺點,但他自己私下其實也說了,上云的趨勢已經(jīng)不可阻擋了,很擔(dān)心有一天會失業(yè) 。
之前有一位讀者提到下面這樣的問題:
我的回答是像這些其實云廠商都提供了很成熟的解決方案,基本一鍵生成,于是就有了這位讀者的靈魂拷問。
我相信很多人也有這有這樣的疑問,我的看法是云服務(wù)廠商這些提供的只是工具,但如何利用好這些工具,還需要我們來操控,所以我給的建議如下:
開發(fā)如果想向更高階發(fā)展,還是要掌握扎實的理論基礎(chǔ)與實戰(zhàn)經(jīng)驗,這就好比,給你一把屠龍刀,沒有深厚的內(nèi)功你能揮得動,揮得好嗎?
本文轉(zhuǎn)載自微信公眾號「碼?!梗梢酝ㄟ^以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系碼海公眾號。