記一次完整的上雲經歷

大家好,我是坤哥

好久沒更了,最近幾周身體不好,得了比較嚴重的胃炎+心動過速症狀,跑了好幾趟醫院,嚴重的時候心臟感覺很不舒服,有瀕死感,胸悶氣短,有時幾乎整夜睡不好覺,在此奉勸大家還是要保重身體,千萬不要做熬夜等傷身體的傻事,千萬保重身體!

年前和年後我們完成了一次從 0 到 1 的上雲之旅,其中踩了不少坑,也積累了不少寶貴的經驗,所以在此總結成文,相信大家看了肯定有收穫

先說下此次上雲的背景,創業後,我們的業務是從集團中獨立出來了,不過系統還是和集團共用的,共用系統本來也不是個事兒,但由於集團早已今非昔比,核心人員都走得差不多了,導致一些核心系統不穩定,甚至出現過反向代理層宕機無人修復而導致整個交易跌零的嚴重事故,所以我們決定將系統完全從集團中剝離出來,由於之前集團系統上雲採用的是騰訊雲,所以我們也用了騰訊雲,這樣網絡可以做到互通,共用一些待遷移的系統的鏡像,也可以用騰訊雲的工具對數據進行增量/全量遷移等以降低遷移成本

雲服務都有哪些

現在上雲可以說是業界公認的趨勢了,因爲像阿里雲,騰訊雲等雲廠商提供的工具真的太全了,基本上覆蓋了一個大廠系統所需的方方面面,不信?我們不妨一起來看看,先來看下大廠的基本系統架構:

可以看到一個完整的可運行的系統需要提供:DNS 解析,CDN 服務,接入層,中間件,存儲層,APM 等,雲上這些工具全有 ,而且基本都是一鍵部署,這裏簡單舉兩個例子

  1. 以部署 ZK 集羣爲例,如果你要部署一個 ZK 集羣,那一般要在三臺虛擬機上部署(ZK集羣要求至少提供三臺服務器),還需要編輯配置文件等,涉及到這種人爲的工作往往比較容易出錯,但在騰訊雲上點個按鈕就可以自動幫你生成一個 ZK 集羣,你所要做的只需在工程中替換此 ZK 集羣地址,同時還可以查看它的基本信息(部署架構),數據管理運行監控(JVM,連接數,內存使用率等),運行日誌

  2. 再比如你要部署一個 Redis 分片集羣,一開始可能只需要一個分片,但後面隨着業務的發展,需要進行分片擴容,那就比較麻煩了,一般需要用官方提供的 redis-trib 管理軟件進行遷移,涉及到創建新的節點,將新的主節點加入集羣轉移slot(重新分片)將從節點加入集羣這些步驟,很煩瑣,但如果用騰訊雲本身提供的工具,只要選擇對應的分片選項,再點確定,即可一鍵搞定(如下),非常方便,同樣的,騰訊雲也提供了 Redis 的緩存命中率慢查詢CPU 使用率等監控

以上只是簡單舉了兩個例子,事實上,像 MySQL,ES,MQ 等組件騰訊雲上也基本都提供了一鍵式生成的操作,並且都附帶了相關的監控與告警機制,極大地降低了運維成本,使用這些工具唯一的缺點就是

但我們也有一些控制成本的手段 ,比如:

  1. 如果項目是內網的話(如運營中心等),完全可以把這些項目都部署在同一臺低配的虛擬機上以節省成本
  2. 線上多個前端項目也可以同時部署在同一臺機器上,配合 CDN 可以解決訪問過慢的問題
  3. 我們需要部署 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 實例
    // RedisCacheClient 即集團 Redis 實例,使用 AOP 的方式可以讓集團的 Redis 寫入的同時也同步到我司的 Redis 實例來
    // 從而最終實現數據一致性
      @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 的創建和 ZK 集羣等創建一樣方便,我們要做的,只是確定好分區數等,確定好之後動動手指點擊一下,這樣就會一鍵生成一個高可用的 RocketMQ 集羣,包括各種監控,消息狀態查閱等也應有盡有,如果你自己部署估計要搞半天,在遷移的時候,我們做了兩手準備,一是同時向集團和我司的 broker 發消息,然後灰度一部分用戶,這部分用戶只向我司的 broker 發消息,確認沒問題後,再停掉髮集團的 broker 邏輯,這樣可以做到平穩過渡

Ansible 簡介

雖然雲上有很多服務可以幫助我們快速接入 Redis 等中間件,快速遷移 DB 等數據,但除了這些基本上雲服務可以提供的並不是說我們其他啥也不用幹了,接下來我們來談下本文的重點:項目部署的架構設計。比如一個 Java 項目,你要跑起來,總得先編譯打包(生成 jar 包)吧,打包之後發佈總不能立馬中斷正在運行的服務吧,你得用優雅停機的方式來停掉服務然後再部署新包,部署之後如果發現有問題要回滾吧,這些步驟如果用手工操作肯定不現實,最好的方式其實是寫成腳本的方式然後一鍵部署,不過一個服務可能有多臺機器,難道我們需要一臺臺登錄然後再手動觸發相應的部署腳本?顯然不現實,所以我們需要一個批量部署的自動化運維工具,經過調研,我們選擇了 Ansible

什麼是 Ansbile ,它有什麼優勢?

Ansible是一款簡單的運維自動化工具,只需要使用ssh協議連接就可以實現批量系統配置批量程序部署批量運行命令等功能

ansbile 有以下幾個優勢:

  1. 它是通過 SSH 來接管對應機器的控制權的,對應的機器無需安裝任何的 ansible 客戶端,也無需啓用額外的服務,所以即便 ansible 升級也不影響對應的機器
  2. 有大量常規運維操作模塊,可實現日常絕大部分操作
  3. 配置簡單、功能強大、擴展性強;
  4. 通過 Playbooks(劇本) 來定製強大的配置、狀態管理,所謂劇本,即 YAML 格式文件,多個任務定義在此文件中,定義主機需要用哪些模塊(主要有核心模塊和自定義模塊)來完成這些功能

由於它的上述這些特點,ansible 很快流行了起來,甚至可以說是運維必備的一款神器了,上圖是 ansible 的極簡版,我們再稍微展開一下它的架構看看

它的執行流程如下

  1. 用戶登錄(一般通過跳板機) ansible 所在機器

  2. 通過 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.410.100.1.5即屬於同一個服務 operator_center,如果我們到時要發佈這個服務的話,只需要指定其服務名 operator_center 即可(下文會介紹)

  3. 通過步驟 2 我們即可指定需要操控哪些機器,然後 ansible 再通過連接模塊(即 Connection Plugins,採用 SSH 連接)連接步驟 2 中指定的 host 然後利用核心模塊(core modules)等來執行我們寫好的 Playbooks(劇本)

ansible 的 core modules(核心模塊)有很多,功能也很強大,基本不需要自定義模塊,像我們這次上雲也只用了核心模塊,來看幾個比較常見的模塊

  1. shell模塊:可以在遠程主機上調用 shell 解釋器運行命令,支持 shell 的各種功能,例如管道等。
  2. copy 模塊:將文件複製到遠程主機,同時支持給定內容生成文件和修改權限等
  3. file 模塊:設置文件的屬性,比如創建文件、創建鏈接文件、刪除文件等
  4. fetch模塊:從遠程某主機獲取(複製)文件到本地(即 ansible 所在機器)
  5. command 模塊:在遠程主機上執行命令,並將結果返回到調用機上(也就是 ansible 所在主機)
  6. cron 模塊:定時任務模塊,這個大家應該比較熟悉了

我們知道一般工程都需要構建(或者說打包,兩個概念相差不大)之後才能部署,比如 Java 工程要打包成 Jar 文件然後再部署(執行 Jar 包),前端工程也需要打包後才能部署(比如把多個 js 文件合併成一個以減少請求提升性能,再比如你可能使用 SCSS 或 less 來寫 CSS,也需要編譯成 CSS 文件,並且合併起來),那麼問題來了,能否直接在生產機器上執行打包操作呢?答案顯然是否定的,主要有兩個原因

  1. 打包由於採用了各種優化手段(比如並行打包等)是很耗費 CPU 的,如果在生產上正在對外服務的機器上執行打包操作的話,那麼很可能由於打包時耗費的 CPU 過大而導致當前正在執行的機器出現響應太慢,拒絕請求等問題,這顯然是不可接受的
  2. 服務是以集羣的形式存在的,可能一個服務有好幾臺機器,這些機器部署其實所需的 jar 包完全是一樣的,沒有必要在各個機器上都執行一遍通過的打包操作

部署架構設計

綜上,我們需要一個專門的打包機,將打包的工作交給打包機,打包機打包好之後,我們再把相應的包發到生產的機器上,然後再執行部署腳本,架構模型如下

通過這樣的方式,打包機承擔了所有繁重的活,打包之後,ansible 會通過 fetch 模塊將這些 jar 包拉到本地,然後再通過 push 模塊把 jar 包 push 到服務集羣上的所有機器,然後再執行比較輕量級的部署腳本

提到上文中的諸多模塊,大家可以沒有什麼概念,那麼接下來我們一起來看下 ansible 如何利用上文所述的幾個核心模塊來完成我們所設計的這個打包部署步驟,這樣大家對 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 服務
         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                                                        # 執行打包腳本
         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
  1. 部署 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 反對上雲,說了一大堆上雲的缺點,但他自己私下其實也說了,上雲的趨勢已經不可阻擋了,很擔心有一天會失業 。

之前就有一位讀者提到下面這樣的問題

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

image-20220310214859569
image-20220310214859569

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章