天天看點

國内最具影響力科技創投媒體36Kr的容器化之路

本文由1月19日晚36Kr運維開發工程師田翰明在Rancher技術交流群的技術分享整理而成。微信搜尋rancher2,添加Rancher小助手為好友,加入技術群,實時參加下一次分享~
田翰明,36Kr 運維開發工程師,在 36Kr 主要負責運維自動化,CI/CD 的建設,以及應用容器化的推動。

背景

36Kr是一家創立于2010年,專注于科技創投領域的媒體公司,業務場景并不複雜,前端主要使用NodeJS進行Render,移動端有Android也有iOS,後端服務幾乎全都由PHP來支援。使用PHP的主要原因是在最初進行技術選型的時候發現,PHP進行Web開發效率比較高,後來就一直這樣延續下來了。

但是在後期,随着業務的突飛猛漲,在程式設計中又沒能進行解耦,就導緻了許多服務耦合成了一個很臃腫的單體應用,邏輯耦合嚴重,進而導緻了很多的性能問題,随着問題越來越難改,開發任務又越來越緊,就不得不往後拖,越往後拖留下的問題就更難改,形成了一個惡性循環,留下了很多的技術債,很不利于後續的開發任務,并且一旦出現了問題,也很難追溯具體原因,是以在那時候經常聽到一句話 “這是曆史遺留問題” 。

B/S、C/S、單體應用,這是一種很傳統 也很簡單的架構,但是缺點也暴露無遺,是以經常因為一個業務邏輯的性能問題,進而影響到所有的業務。在運維側,運維隻能夠通過堆機器,升配置等政策來應對,投入了很多的機器成本和人力成本,但是收效甚微,很是被動。

這種情況已經是迫在眉睫了,終于技術團隊決定使用 Java 語言進行重構,将單體應用進行微服務化拆解,徹底改變這種因為單體應用故障而導緻生産環境出現大範圍的故障。

需求分析 + 選型

在重構計劃開始一段時間後,為了節省虛機資源,我們一台虛機上運作了多個 Java 程式,但是因為沒有資源隔離和靈活的排程系統,其實也會導緻一些資源的浪費。并且在高并發場景下,偶爾會有資源搶占導緻一個應用影響另一個應用的情況。為此,我們運維專門開發了一套自動化部署系統,系統内包括部署、監控檢測、部署失敗復原、重新開機等基礎功能。

随着當時 K8s 的風靡,還有 Rancher 2.x 的釋出,我們逐漸發現,我們所面臨的這些問題,它們基本都能解決,比如資源隔離、deployment 的控制器模型、靈活的排程系統,這些都有,這就是最好的自動化部署系統啊,于是我們運維側,也開始決定向容器化進軍。

在選型上,因為我們的服務基本都在阿裡雲上面,是以第一個想到的是阿裡雲。時因為我們和華為有一些業務的往來,是以華為的 CCE 也作為了備選,但是考慮到我們的服務資源全部在阿裡雲上,這個遷移成本實在太大了,是以就沒再考慮華為雲。

我們一開始使用過Rancher 1.6,但是隻是用來管理主機上部署的原生 Docker。也是以對Rancher的産品産生了很大的好感。

需求方面,因為要降低我們研發人員的學習成本,容器管理平台的易用性十分重要。此外,K8s 的基礎功能是必須的,因為 K8s 還在高速發展階段,是以能需要夠随時跟上更新,有安全漏洞後也需要第一時間進行更新打更新檔,同時還要有基本的權限控制。而且我們公司内部沒有專門的K8S團隊,運維人員也隻有2位,是以如果能夠有專業人員進行技術上的交流,發生了問題可以有專業的服務團隊來協助也十分重要。

綜上,基本上就是 Rancher 完勝,UI 做得非常友好,開發人員能夠很快上手,更新疊代速度也非常快,發現漏洞後也會有詳細的更新檔方案,認證政策也完美支援我們的 OpenLDAP 協定,能夠對開發、測試、運維人員進行不同權限控制,并且也是第一家做到支援多雲環境的,友善以後我們做跨雲的方案。

我們這次容器化的過程,主要經曆了以下幾個因素的考慮,今天我就來和大家分享我們在 Rancher 上的一些實踐,希望能給大家帶來幫助:

  • 應用的容器化改造
  • Rancher 的高可用性
  • 容器的運維
  • 多租戶隔離

因為我們的開發人員,有相當一部分是沒有接觸過容器的,為了能對開發人員更友好一些,我們的鏡像分成了兩層,主要的 Dockerfile 編寫是由我們運維人員來編寫的,而開發人員代碼倉庫裡的 Dockerfile 是最簡單的,基本上隻有代碼拷貝的過程和一些必傳的變量,具體可以參考以下示例:

## 這是運維人員維護的 Dockerfile 示例
## 本示例僅做參考
FROM alpine:3.8
MAINTAINER yunwei <[email protected]>
WORKDIR /www
RUN mv /etc/apk/repositories /etc/apk/repositories.bak \
    && echo "http://mirrors.aliyun.com/alpine/v3.8/main/" >> /etc/apk/repositories \
  && apk update && apk upgrade
RUN apk --no-cache add ca-certificates wget && \
    wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub && \
    wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.29-r0/glibc-2.29-r0.apk && \
    apk add glibc-2.29-r0.apk && rm -f glibc-2.29-r0.apk
RUN apk add -U --no-cache \
  bash \
  sudo \
  tzdata \
  drill  \
  iputils \
    curl \
  busybox-extras \
  && rm -rf /var/cache/apk/* \
  && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
COPY java-jar/jdk1.8.0_131 /usr/local/jdk1.8.0_131
ENV TZ="Asia/Shanghai"
ENV JAVA_HOME=/usr/local/jdk1.8.0_131
ENV CLASSPATH=$JAVA_HOME/bin
ENV PATH=.:$JAVA_HOME/bin:$PATH
ENV JAVA_OPTS="-server -Xms1024m -Xmx1024m"
CMD java -jar $JAVA_OPTS -Dserver.port=8080 server.jar

=======================================

## 這是開發人員維護的 Dockerfile 的示例
FROM harbor.36kr.com/java:v1.1.1
MAINTAINER developer <[email protected]>
ADD web.jar ./server.jar
           

可以看到,開發人員所維護的 Dockerfile 可以說相當簡單了,這大大的降低了開發人員維護的難度。

另外,因為建構産物的大小,很大程度上決定了部署時間的長短,是以我們使用了号稱最小的鏡像——alpine,alpine 有很多的優點:

  • 體積小
  • 有包管理器、有豐富的依賴
  • 大廠的支援,包含 Docker 公司在内的多家大廠官方使用

但是他有一個缺點,alpine 上并沒有 glibc 庫,他所使用的是一個 musl libc 的小體積替代版,但是 Java 是必須依賴的 glibc 的,不過早就有大神了解了這點,在 GitHub 上已經提供了預編譯的 glibc 庫,名字為

alpine-pkg-glibc

,裝上這個庫就可以完美支援 Java,同時還能夠保持體積很小。

安裝 Rancher 的方式有兩種:單節點安裝和高可用叢集安裝。一般單節點安裝僅适用于測試或者 demo 環境,是以要正式投入使用的話,還是推薦高可用叢集的安裝方式。

我們一開始測試環境就使用了單節點安裝的方式,後來因為 Rancher Server 那台機器出現過一次重新開機,就導緻了測試環境故障,雖然備份了,但是還是丢失了少量資料,最後我們測試環境也采用了 HA 高可用部署,整個架構如下圖所示。

Rancher Server 我是采用的 RKE 安裝,并且為了防止阿裡雲出現區域性的故障,我們将 Rancher Server 的三台機器,部署在了兩個可用區,Rancher Server-001、003 在北京的 H 區、Rancher Server-002 在北京的 G 區。

負載均衡,我們采用的是阿裡雲的 SLB,也是采購的主備型執行個體,防止單點故障,因為 Rancher 必須使用 SSL 證書,我們也有自己的域名證書,為了友善在 SLB 上進行 SSL 證書的維護,我們使用的是 7 層協定,在 SLB 上做的 SSL 終止,Rancher Server 的架構圖可以參考下圖:

國内最具影響力科技創投媒體36Kr的容器化之路

下遊叢集,也就是用來承載業務的 K8s 叢集,我們也是一半一半,在阿裡雲的兩個可用區進行部署的,需要注意的是,為了保證兩個區的網絡時延 <= 15 ms,這就完成了一個高可用的災備架構。

備份方面,我們也使用了阿裡雲 ECS 快照 + ETCD S3 協定備份到了阿裡雲的 OSS 對象存儲兩種方案,確定出現故障後,能夠及時恢複服務。

部署的詳細教程可以參考 Rancher 官方文檔。

容器的運維,這裡主要指容器的日志收集和容器監控,容器監控方面呢,Rancher 自帶了 Prometheus 和 Grafana,而且和 Rancher 的 UI 有一些整合,就非常的友善,是以監控方面我就不展開講了,我主要說一說日志收集。

在 K8s 裡,日志的收集相比傳統的實體機、虛機等方式要複雜一些,因為 K8s 所提供的是動态的環境,像綁定 hostpath 這種方式是不适用的,我們可以通過以下這個表格直覺的對比一下:

國内最具影響力科技創投媒體36Kr的容器化之路

可以看到,K8s 需要采集的日志種類比較多,而容器化的部署方式,在單機器内的應用數是很高的,而且都是動态的,是以傳統的采集方式是不适用于 K8s 的。

目前 K8s 的采集方式大體可以分為兩種,被動采集和主動推送。

主動推送一般有 DockerEngine 和 業務直寫兩種方式:DockerEngine 是 Docker 的 LogDriver 原生自帶的,一般隻能收集 STDOUT、一般不建議使用;而業務直寫,則需要在應用裡內建日志收集的 SDK,通過 SDK 直接發送到收集端,日志不需要落盤,也不需要部署Agent,但是業務會和 SDK 強綁定,靈活性偏低,建議對于日志量較大,或者對日志有定制化要求的場景使用。

被動推送是采用部署日志收集 Agent 進行采集的,有兩種方式,一種是 Daemonset 每個機器節點上部署一個 Agent,還有一種 Sidecar,每個 Pod 以 Sidecar 的形式部署一個 Agent。

Sidecar 部署方式比較消耗資源,相當于每個 Pod 都有一個 agent,但是這種方式 靈活性以及隔離性較強,适合大型的 K8s 叢集或者作為 PaaS 平台為業務方提供服務的群使用,Daemonset 部署方式,資源消耗較小,适合功能單一、業務不多的叢集。

結合我們自身的場景,屬于小規模叢集,并且業務也不算多,我們選擇了 Daemonset 的部署方式,在測試環境,我們經過調研選擇了阿裡開源的一個日志收集元件log-pilot GitHub 位址是:github.com/AliyunContainerService/log-pilot ,通過結合 Elasticsearch、Kibana 等算是一個不錯的 K8s 日志解決方案。

因為我們的伺服器都在阿裡雲上,我們運維人員比較少隻有2位,沒有精力再去維護一個大型的分布式存儲叢集,是以我們的業務日志選擇存儲在了阿裡雲的日志服務,是以在生産環境,我們的 K8s 也使用了阿裡雲日志服務,目前單日日志 6億+ 沒有任何問題。

使用阿裡雲收集日志呢,你需要開通阿裡雲的日志服務,然後安裝 Logtail 日志元件

alibaba-log-controller Helm

,這個在官方文檔裡有安裝腳本,我把文檔連結貼在下面,在安裝元件的過程中會自動建立aliyunlogconfigs CRD,部署

alibaba-log-controller

的Deployment,最後以 DaemonSet 模式安裝 Logtail。然後你就可以在控制台,接入你想要收集的日志了。安裝完以後是這樣的:

國内最具影響力科技創投媒體36Kr的容器化之路

Logtail支援采集容器内産生的文本日志,并附加容器的相關中繼資料資訊一起上傳到日志服務。Kubernetes檔案采集具備以下功能特點:

  • 隻需配置容器内的日志路徑,無需關心該路徑到主控端的映射
  • 支援通過Label指定采集的容器
  • 支援通過Label排除特定容器
  • 支援通過環境變量指定采集的容器
  • 支援通過環境變量指定排除的容器
  • 支援多行日志(例如java stack日志)
  • 支援Docker容器資料自動打标簽
  • 支援Kubernetes容器資料自動打标簽

如果你想了解更多,可以檢視阿裡雲日志服務的官方文檔:

https://help.aliyun.com/document_detail/157317.html?spm=a2c4g.11186623.6.621.193c25f44oLO1V

容器的多租戶隔離

我這裡所講的,主要指的是企業内部使用者的多租戶隔離,而不是指的 SaaS、KaaS 服務模型的多租戶隔離。

在權限方面,因為我司對于權限的管控較嚴格,而 Rancher 恰好提供了非常友善的基于 叢集、項目、命名空間等多個粒度的權限控制,并且支援我司基于 OpenLDAP 的認證協定,非常便于管理,我可以給不同項目組的開發、測試人員開通相對應的 叢集/項目/命名空間的權限。

比如下圖,我可以給叢集添加使用者、也可以給某個 Project 添加使用者,并且可以指定幾個不同的角色,甚至可以自定義角色。

國内最具影響力科技創投媒體36Kr的容器化之路

比如場景1:我可以給 項目組長,配置設定開發環境叢集->項目1 所有者(Owner)權限,然後項目組長可以自由控制給本項目添加他的成員,并配置設定相應權限。

場景2:我可以給 測試經理,配置設定測試叢集的所有者(Owner)權限,由測試經理來配置設定,誰來負責哪個項目的測試部署,以及開發人員隻能檢視日志等。

在資源方面,一定要進行容器的資源配額設定,如果不設定資源限額,一旦某一個應用出現了性能問題,将會影響整個 node 節點上的所有應用,K8s 會将出現問題的應用排程到其他 node 上,如果你的資源不夠,将會出現整個系統的癱瘓,導緻雪崩。

國内最具影響力科技創投媒體36Kr的容器化之路

Java 應用的資源配額限制也有一個坑,因為預設 Java 是通過

/proc/meminfo

來擷取記憶體資訊的,預設 JVM 會使用系統記憶體的

25%

作為

Max Heap Size

,但是容器内的

/proc/meminfo

是主控端隻讀模式挂載到容器裡的,是以采取預設值是行不通的,會導緻應用超過容器限制的記憶體配額後被OOM,而健康檢查又将服務重新開機,造成應用不斷的重新開機。

那是不是通過手動參數設定

JVM 記憶體 = 容器記憶體限額

呢?不行!因為 JVM消耗的記憶體不僅僅是 Heap,因為 JVM 也是一個應用,它需要額外的空間去完成它的工作,你需要配置的限額應該是

Metaspace + Threads + heap + JVM 程序運作所需記憶體 + 其他資料

關于這塊,因為涉及到的内容較多,就不進行展開,感興趣的同學可以自己去搜尋 一下。

總 結

因為我們的業務場景并不複雜,是以我們的容器化之路,其實走的也相對來講蠻順暢的,我們的運維人員很少,隻有 2 位,是以我們也沒有太多的時間精力去維護太多的自建系統,我們使用了很多的阿裡雲産品,包括 Rancher,他很友善的部署方式,友好的 UI,包括內建好的監控等等,在容器化之路上給了我們很大的信心。

我們使用建構兩層鏡像的方式,降低了開發人員的學習複雜度。使用了小體積鏡像 alpine + 預編譯 glibc 減小了鏡像體積。提高了部署的時間,在架構上,我們采用了阿裡雲雙區機房的災備的架構,以及完備的備份方案。使用 Daemonset 部署的日志收集元件,收集到阿裡雲日志服務,支撐我們 6億/日的日志系統。Rancher 還提供給了我們深度內建的監控系統、多租戶隔離等。還有我們自己踩坑 踩出來的資源配額設定。

其實容器化并不複雜,如果沒有 K8s,我們需要自己建構健康監測系統、發版系統、維護不同的主機環境,不能細粒度的進行資源劃分,不能更有效的利用計算資源,運維的工作主要是什麼?在我看來其實就是 節約成本、提高效率。虛拟化、自動化、智能化、高性能、高可用、高并發 等等,這些無一不是圍繞着成本和效率這兩個詞,而 K8s 其實已經幫我們都做好了,而像 Rancher 這種編排平台又幫我們降低了 K8s 的學習複雜度,是以你要做的就是加入 K8s,好了,到這裡這次的分享就結束了。感謝~

社群QA

Q1:K8S在生産環境的高可用存儲方案有推薦嗎?

A1:存儲方案沒有标準答案,我們主要使用阿裡雲,是以用的是阿裡雲的塊存儲,比較常見的方案還有 Ceph、GlusterFS、Portworx、OpenEBS 等,他們各有優劣,需結合自己的業務需求進行選擇

Q2:灰階釋出,Kubernetes網絡流量可以通過服務網格分流實作網絡層面的分發,但是涉及到應用大版本的更新時候,涉及到資料庫結構的變更的時候,如何實作灰階釋出?

A2:沒有遇到過這個場景,不過提供一個思路,可以準備兩套資料庫,網絡分流也可以分流到不通資料庫,具體需要你自己驗證一下是否可行

要厘清楚這是兩層,一層是邏輯層,一層是資料層,不能混為一談

Q3:Pipeline是用什麼做的?Pipeline下,如何處理同一個分支,需要并行測試多個版本的場景?我用Rancher的Pipeline,局限性比較大,就是同一個分支無法并行多套進行測試。命名空間在使用,但是同一個分支下,命名空間是寫在.rancher.yml下的,是以無法區分,Rancher的Pipeline不能在外面注入變量進行區分。

A3:Rancher 的 Pipline 目前還是有一些不夠靈活,我們使用的是自建 Jenkins 做 Pipeline 的,并行測試,可以用命名空間等隔離政策進行隔離,或者準備多套測試環境

Q4: 你們運維的Dockerfile和開發的Dockerfile是怎麼合并的?

A4:開發的 Dockerfile 是 From 運維的 Dockerfile

Q5:你們k8s的漏洞掃描用的什麼工具?一般什麼級别的鏡像漏洞需要進行修複?

A5:暫時沒有使用漏掃工具,我們主要根據 Rancher 企業服務通知的修複建議進行修複

Q6: 就是比如說從外網,通過service ip能夠登陸并且管理容器。想實作這一步必須通過将service ip暴露出來,然後這個service ip怎麼暴露出來?麻煩解答一下。

A6:如果需求是管理容器,其實可以使用 Rancher 的使用者權限控制,讓某一使用者擁有某一容器的權限,暴露 service ip 到公網,讓使用者管理容器是無法實作的

Q6 : 好的,謝謝,我還有一點不明白,這個service ip有什麼辦法能讓他暴露出來呢?你意思是說讓不同的使用者通過rancher平台去管理不同的容器嗎?麻煩再給解答一下,謝謝。

A6:可以使用 NodePort 暴露,通過 Node ip 和 端口進行通路,或者使用 公有雲的負載均衡産品

Q6 : 我不是這個意思,我是想把service ip暴露出來,不隻單單想通過叢集内部通路。

A6:service ip 本來就是 K8s 内部的,暴露不了,隻能轉發

Q7: 為何沒有放在3個可用區,如果可用區H挂掉,是否會導緻叢集不可通路?

A7:3個可用區當然也是可以的,Rancher HA 架構,隻要有一個 Server 可用就沒有關系

Q8:請教下你們多套開發測試環境的pipeline是怎麼樣的流程呢 (差異化)?有使用helm template嗎,友善講解下更多細節麼?

A8:目前是通過 Jenkins 部署參數,部署的時候可以選擇 命名空間、環境辨別、分支等,通過 sed 修改 template

Q9:請問你們的devops流是怎樣的呢?一個環境對應一個docker鏡像,還是說test pre prd共用一個docker鏡像呢?如果是一個docker鏡像共用test pre prd的話是怎麼做的呢(比如不同環境的配置以及開發的協‘同開發流)?

A9:我們是用的同一個鏡像,部署時通過選擇不同的環境辨別參數,程式會自動注入不同環境的配置,需要開發進行一些相應的配置修改

Q10:不大懂容器的資源限制該如何配置,自己配置了感覺不起作用

A10:Rancher 可以在項目、命名空間、Pod 三個粒度進行設定,優先級相反

繼續閱讀