
Statefulset名稱為web 三個Pod副本: web-0,web-1,web-2,volumeClaimTemplates名稱為:www,那么自動創建出來的PVC名稱為www-web[0-2],為每個Pod創建一個PVC。
Kubernetes StatefulSet 允許我們為 Pod 分配一個穩定的標識和持久化存儲,Elasticsearch 需要穩定的存儲來保證 Pod 在重新調度或者重啟后的數據依然不變,所以需要使用 StatefulSet 來管理 Pod。

一、StatefulSet的設計原理
首先我們先來了解下Kubernetes的一個概念:有狀態服務與無狀態服務。
無狀態服務(Stateless Service):該服務運行的實例不會在本地存儲需要持久化的數據,並且多個實例對於同一個請求響應的結果是完全一致的。這種方式適用於服務間相互沒有依賴關系,如Web應用,在Deployment控制器停止掉其中的一個Pod不會對其他Pod造成影響。
有狀態服務(Stateful Service):服務運行的實例需要在本地存儲持久化數據,比如數據庫或者多個實例之間有依賴拓撲關系,比如:主從關系、主備關系。如果停止掉依賴中的一個Pod,就會導致數據丟失或者集群崩潰。這種實例之間有不對等關系,以及實例對外部數據有依賴關系的應用,就被稱為“有狀態應用”(Stateful Application)。
其中無狀態服務在我們前面文章中使用的Deployment編排對象已經可以滿足,因為無狀態的應用不需要很多要求,只要保持服務正常運行就可以,Deployment刪除掉任意中的Pod也不會影響服務的正常,但面對相對復雜的應用,比如有依賴關系或者需要存儲數據,Deployment就無法滿足條件了,Kubernetes項目也提供了另一個編排對象StatefulSet。
StatefulSet將有狀態應用抽象為兩種情況:
拓撲狀態。這種情況意味着,應用的多個實例之間不是完全對等的關系。這些應用實例,必須按照某些順序啟動,比如應用的主節點 A 要先於從節點 B 啟動。而如果你把 A 和 B 兩個 Pod 刪除掉,它們再次被創建出來時也必須嚴格按照這個順序才行。並且,新創建出來的 Pod,必須和原來 Pod 的網絡標識一樣,這樣原先的訪問者才能使用同樣的方法,訪問到這個新 Pod。
存儲狀態。這種情況意味着,應用的多個實例分別綁定了不同的存儲數據。對於這些應用實例來說,Pod A 第一次讀取到的數據,和隔了十分鍾之后再次讀取到的數據,應該是同一份,哪怕在此期間 Pod A 被重新創建過。這種情況最典型的例子,就是一個數據庫應用的多個存儲實例。
StatefulSet 的核心功能,就是通過某種方式記錄這些狀態,然后在 Pod 被重新創建時,能夠為新 Pod 恢復這些狀態。它包含Deployment控制器ReplicaSet的所有功能,增加可以處理Pod的啟動順序,為保留每個Pod的狀態設置唯一標識,同時具有以下功能:
穩定的、唯一的網絡標識符
穩定的、持久化的存儲
有序的、優雅的部署和縮放
二、有狀態服務的拓撲狀



https://www.cnblogs.com/erlou96/p/13803188.html 查看博客https://www.cnblogs.com/erlou96/p/13803188.html
一.准備基礎環境
1.jdk
2.zookeeper 自行下載
3.將這些包都放在/opt/docker 目錄下,這個目錄后續作為Dockerfile的啟動目錄
二.安裝docker
1安裝docker
2 更改DOCKER 存儲目錄
因為后續還要制作別的鏡像,根目錄容量不夠,需要修改docker的掛載路徑
解決方法:參考https://blog.csdn.net/justlpf/article/details/103716138
根據docker服務的安裝配置文件進行修改
在ExectStart=xxx 中添加屬性
重新更新一下docker服務
三.下載基礎鏡像
1.下載centos7.2的基礎鏡像
2. 查看docker 鏡像
四.制作zookeeper環境包
1.解壓文件
2.創建一個啟動zookeeper的腳本
這里一定要加上前台啟動命令,否則docker會異常退出Docker容器啟動web服務時,都指定了前台運行的參數,例如apache:
-
ENTRYPOINT [ "/usr/sbin/apache2" ]
-
CMD ["-D", "FOREGROUND"]
又例如nginx:
-
ENTRYPOINT [ "/usr/sbin/nginx", "-g", "daemon off;" ]
因為Docker容器僅在它的1號進程(PID為1)運行時,會保持運行。如果1號進程退出了,Docker容器也就退出了。
一定要注意daemon off ;分號不能去掉!否則失敗!!
3.創建zookeeper data數據目錄
4.創建zoo.cfg文件,進入zookeeper 目錄
5.修改配置文件zoo.cfg的內容
6.將修改后的zookeeper文件重新打成tar包
將這個包放到/opt/docker目錄下。 到此:zookeeper的基礎包就制作好了
五.制作jdk環境包
1.安裝jdk
默認的jdk安裝路徑為/usr/java/jdk1.8.0_211
2.將/usr/java/jdk1.8.0_211 文件復制到 /opt/docker 下並改名為jdk
3.將jdk打成tar包
將這個包放到/opt/docker目錄下。到此:jdk的環境包准備好了
六.編寫Dockerfile文件
1.創建dockerfile文件
# FROM命令 定義基礎包 FROM docker.io/centos:centos7.2.1511 # ADD命令 將打包文件上傳到鏡像的根目錄/ ,會自動解壓 ADD zookeeper.tar /opt ADD jdk.tar /opt # WORKDIR命令 定義工作目錄 WORKDIR /opt # ENV命令 設置環境 ENV JAVA_HOME /opt/jdk1.8.0_211 ENV CLASSPATH .:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar ENV PATH $JAVA_HOME/bin:$PATH # RUN命令 執行制作鏡像過程,一個RUN對應一層 #RUN yum clean all \ RUN rm -vf /etc/localtime \ #&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime\ #&& rm -rfv /usr/share/backgrounds/* \ #&& rm -rfv /usr/share/doc/* \ #&& rm -rfv /usr/share/man/* \ #&& cd /usr/share/zoneinfo/ && ls |grep -v "Asia"|xargs rm -rfv \ #&& rm -rfv /var/cache/yum/* \ #&& rpm --rebuilddb \ && chmod 755 /opt/zookeeper/start.sh ENTRYPOINT /opt/zookeeper/start.sh
七.構建鏡像
1.在/opt/docker目錄下構建鏡像
2.查看鏡像
3.啟動容器:映射端口,並且將Docker掛載本地目錄及實現文件共享,這樣重啟容器,zk的數據不會丟失
八.驗證zk是否啟動成功
啟動成功,問題解決
參加博客2:https://blog.csdn.net/cool_summer_moon/article/details/106490067
參加博客3:https://blog.csdn.net/Happy_Sunshine_Boy/article/details/107249542
1.Dockerfile
1.1 環境准備
apache-zookeeper-3.6.1-bin.tar.gz
jdk-8u121-linux-x64.tar.gz
zookeeper.DockerFile
# 基礎鏡像 生成的鏡像作為基礎鏡像 FROM centos # 指定維護者的信息 MAINTAINER tanggaomeng<tanggaomeng@inspur.com> # 復制並解壓 ADD jdk-8u121-linux-x64.tar.gz /usr/local/ ADD apache-zookeeper-3.6.1-bin.tar.gz /usr/local # 配置環境 RUN yum -y update RUN yum -y install vim net-tools telnet tree git wget curl ENV work_path /usr/local WORKDIR $work_path # java ENV JAVA_HOME /usr/local/jdk1.8.0_121 ENV JRE_HOME /usr/local/jdk1.8.0_121/jre ENV CLASSPATH .:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib # zookeeper ENV ZOOKEEPER_HOME /usr/local/apache-zookeeper-3.6.1-bin # 環境變量設置 ENV PATH $PATH:$JAVA_HOME/bin:$JRE_HOME/bin:$ZOOKEEPER_HOME/bin RUN cp $ZOOKEEPER_HOME/conf/zoo_sample.cfg $ZOOKEEPER_HOME/conf/zoo.cfg EXPOSE 2181 CMD $ZOOKEEPER_HOME/bin/zkServer.sh start-foreground
鏡像說明: FROM centos是一個基礎鏡像,后面的操作等於我們登錄到了centos操作系統上面執行后續的操作,
ADD鏡像拷貝,拷貝到centos的根目錄上面根目錄為/,add默認會有個自動解壓的操作,copy不會默認自動解壓。COPY命令用於將於Dockerfile所在目錄中的文件在鏡像構建階段從宿主機拷貝到鏡像中,對於文件而言可以直接將文件復制到鏡像中,add除了copy的作用還有自動對tar進行解壓
默認的workdir是centos的根目錄,上面的dockfile中指定work_path為 /usr/local,命令
后續所有命令的執行都是在wordir目錄pwd該目錄下執行的,WORKDIR指令設置Dockerfile中的任何RUN,CMD,ENTRPOINT,COPY和ADD指令的工作目錄。
單個Dockerfile可以使用多次WORKFDIR。如果提供一個相對路徑,當前的工作目錄將於上個WORKDIR指令相關。如:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
pwd命令的輸出/a/b/c
執行命令執行命令docker exec的時候,就會去讀取容器的那個workdir,進入到該容器的worddir目錄,這里一定要注意
ENV ZOOKEEPER_HOME 在centos中定義一個環境變量,等容器啟動之后,你可以使用echo輸出改環境變量的值
EXPOSE 2181定義容器啟動之后容器的端口是2181
CMD $ZOOKEEPER_HOME/bin/zkServer.sh start-foreground cmd命令表示容器啟動之后執行的命令,將zookeeper啟動起來
RUN表示在centos鏡像中執行的一系列命令操作,如果要執行多個命令需要使用&&,如下面所示

表示連續執行一系列上面的命令操作
接下來講講docker中cmd和EntryPoint的區別
參加博客:https://blog.csdn.net/f1ngf1ngy1ng/article/details/105546160
https://blog.csdn.net/thedarkclouds/article/details/81982338
相當的經典




我們知道,通過docker run 創建並啟動一個容器時,命令的最后可以指定容器啟動后在容器內立即要執行的指令,如:
docker run -i -t ubunu /bin/bash //表示容器啟動時立即在容器內打開一個shell終端
docker run ubuntu /bin/ps //表示容器啟動后立即運行 /bin/ps命令,顯示容器的當前進程。
除了這種方式外,我們可以在dockerfile文件中通過CMD指令指定容器啟動時要執行的命令。如:
#test
FROM ubuntu
MAINTAINER xxx
RUN echo hello1 > test1.txt
RUN echo hello2 > /test2.txt
EXPOSE 80
EXPOSE 81
CMD ["/bin/bash"]
上面dockerfile文件中最后一行CMD指令的參數是指定容器啟動時要執行的命令,這里是bin/bash命令。
1、用docker run命令創建並啟動容器(myimage 是用前面dockerfile創建的鏡像的名稱):
docker run -i -t myimage
上面命令是創建並啟動容器,打開一個交互式shell。 而以前的寫法是
docker run -i -t myimage /bin/bash
這樣就省去了在docker run中寫命令了。
2、即使dockerfile中有CMD指令,我們仍然可以在docker run命令中帶上容器啟動時執行的命令,這會覆蓋dockerfile中的CMD指令指定的命令。如:
docker run -i -t myimage /bin/ps
上面命令,因為/bin/ps覆蓋了CMD指令,啟動容器時會打印容器內的當前進程,但容器會立即停止,因為/bin/bash被覆蓋了,無法打開交互式shell界面。
3、需要注意的是,dockerfile中可以有多條cmd命令,但只是最后一條有效。
4、CMD命令的參數格式,一般寫成 字符串數組的方式,如上面的例子。如:
CMD ["echo","hello world"]
雖然也可寫成CMD echo hello word 方式,但這樣docker會在指定的命令前加 /bin/sh -c 執行,有時有可能會出問題。 所以推薦采用數據結構的方式來存放命令。
需要注意的是,Entrypoint的命令一定會被執行,不能被外面的命令覆蓋,但是可以有多條cmd命令,但只是最后一條有效,並且cmd命令能夠被dokcer run 后面自帶的命令
覆蓋dockerfile中定義的cmd命令。
1.2 構建鏡像
docker build -f zookeeper.DockerFile -t zookeeper:3.6.1 .
1.3 創建並啟動容器
docker run -d --name zookeeper -p 2181:2181 zookeeper:3.6.1
1.4 連接ZooKeeper容器
docker exec -it zookeeper /bin/bash
1.5 進入ZooKeeper的bin目錄
cd apache-zookeeper-3.6.1/bin
1.6 客戶端連接ZooKeeper服務器端
./zkCli.sh -server 127.0.0.1:2181
1.7 創建節點
[zk: 127.0.0.1:2181(CONNECTED) 1] create -e /test-node 123456
1.8 列出所有根節點
[zk: 127.0.0.1:2181(CONNECTED) 2] ls /
[test-node, zookeeper]
1.9 獲取指定節點的值
[zk: 127.0.0.1:2181(CONNECTED) 3] get /test-node
1.10 斷開客戶端連接
[zk: 127.0.0.1:2181(CONNECTED) 4] quit
# FROM命令 定義基礎包 FROM docker.io/centos:centos7.2.1511 # ADD命令 將打包文件上傳到鏡像的根目錄/ ,會自動解壓 ADD zookeeper.tar /opt
ADD jdk.tar /opt # WORKDIR命令 定義工作目錄 WORKDIR /opt # ENV命令 設置環境 ENV JAVA_HOME /opt/jdk1.8.0_211 ENV CLASSPATH .:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar ENV PATH $JAVA_HOME/bin:$PATH # RUN命令 執行制作鏡像過程,一個RUN對應一層 #RUN yum clean all \ RUN rm -vf /etc/localtime \ #&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime\ #&& rm -rfv /usr/share/backgrounds/* \ #&& rm -rfv /usr/share/doc/* \ #&& rm -rfv /usr/share/man/* \ #&& cd /usr/share/zoneinfo/ && ls |grep -v "Asia"|xargs rm -rfv \ #&& rm -rfv /var/cache/yum/* \ #&& rpm --rebuilddb \ && chmod 755 /opt/zookeeper/start.sh ENTRYPOINT /opt/zookeeper/start.sh



zk的存儲使用外部的gfs集群存儲,在第三節課中首先使用本地存儲來創建pv,這里上面的zk鏡像啟動了三個pod,每一個pod都要存儲對應的數據,這里就應該使用三個節點
在每個節點上面都創建一個pv的目錄來存儲pv,如k8s有三個節點,啟動的三個zk的pod分別部署在三個節點上,pod分別使用三個節點上面的本地存儲,需要給三個節點打上標簽,分表是zk0、zk1,zk2

上面這個pv很經典呀,重點分析下
第一個創建一個pv-zk-test005-zookpeer-0的pv,給pv打上label標簽也是pv-zk-test005-zookpeer-0
整個pv-zk-test005-zookpeer-0的磁盤大小是500M,這里模式是 volumeMode字段的默認值是Filesystem,但也支持配置為Block,這樣就會把node節點的local volume作為容器的一個裸塊設備掛載使用
這里accessMode設置為
RWO:ReadWriteOnce,僅允許單個節點掛載進行讀寫;
ROX:ReadOnlyMany,允許多個節點掛載且只讀;
RWX:ReadWriteMany,允許多個節點掛載進行讀寫;
表示每個節點上面掛載的目錄只需要當前的節點操作,意思就是zk0只能訪問zk0上面的操作,pod-0部署在zk0這個節點上面,那么pod-0只能訪問zk0上面掛載的目錄,不能訪問zk1節點上面的目錄數據,這就是所謂的有狀態操作
1、創建的pod的都是有名稱的,創建的三個pod的名稱列如分別都是pod-1,pod-2,pod-3,如果pod-1異常退出之后,statefu會創建一個新的pod,pod的名稱和之前退出的pod名稱一致都是叫pod-1,pod-1訪問的也是之前zk1節點上面原來就存儲好的數據保證數據的唯一性
hostpath指定當前掛載的目錄是位於當前主機的那個目錄
Retain表示pod異常退出之后對應的數據不會刪除,還會繼續保留,,如果pod-1異常退出之后,statefu會創建一個新的pod,pod的名稱和之前退出的pod名稱一致都是叫pod-1,pod-1訪問的也是之前zk1節點上面原來就存儲好的數據保證數據的唯一性,就是有狀態
這里要指定pv的親和性,pv保存到那個節點上,pv-zk-test005-zookpeer-0這個pv只能存儲在打了zk0標簽的k8s集群的node節點上面
接下來我們來看pvc的定義

pvc就是通過上面的selector要選擇對應的pv
在上面的目錄下一般存儲的zk的data數據和log數據,data中存儲的是zk的myid





headless service 需要將 spec.clusterIP 設置成 None。
因為沒有ClusterIP,kube-proxy 並不處理此類服務,因為沒有load balancing或 proxy 代理設置,在訪問服務的時候回返回后端的全部的Pods IP地址,主要用於開發者自己根據pods進行負載均衡器的開發(設置了selector)。
我們來看一個案例
首先創建一個nginx的編排文件
$cat deployment.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 2 # tells deployment to run 2 pods matching the template template: # create pods using pod definition in this template metadata: # unlike pod-nginx.yaml, the name is not included in the meta data as a unique name is # generated from the deployment name labels: app: nginx_test spec: containers: - name: nginx image: nginx:1.7.9 ports: - containerPort: 80
接下來窗口headless service文件
$ cat headless_service.yaml kind: Service apiVersion: v1 metadata: name: nginx-service spec: selector: app: nginx_test ports: - protocol: TCP port: 80 targetPort: 80 clusterIP: None
通過dns訪問,會返回后端pods的列表
登錄到Cluster的內部pod,解析service的域名返回的就是后端pod的列表
nslookup nginx-service nslookup: can't resolve '(null)': Name does not resolve Name: nginx-service Address 1: 10.10.23.36 Address 2: 10.10.23.39
通過headless service 可以輕松找到statefulSet 的所有節點。
特別是在部署集群的時候,很多服務需要配置節點信息來創建集群。
statefulSet.spec.serviceName
當serviceName 配置成與headless service的Name 相同的時候
可以通過 {PodName}.{headless service}.{namespace}.svc.cluster.local 解析出pod節點IP。
PodName 由 {statefulSet name}-{編號} 組成。
如上,web為我們創建的StatefulSets,對應的pod的域名為web-0,web-1,他們之間可以互相訪問,這樣對於一些集群類型的應用就可以解決互相之間身份識別的問題了。
https://www.jianshu.com/p/a6d8b28c88a2

statefulless啟動的pod名稱是web,啟動兩個pod,pod的編號是有順序的分別是web-0,web1
上web-1是pod的名稱,nginx是headless對應的service的名稱,default是namespace,svc.cluster.local是默認的值,使用
dns解析web-1.nginx.default.svc.cluster.local得到的就是web-1這個pod的IP
curl web-1.nginx.default.svc.cluster.local最終訪問的就是web-1這個pod
如上,web為我們創建的StatefulSets,對應的pod的域名為web-0,web-1,他們之間可以互相訪問,這樣對於一些集群類型的應用就可以解決互相之間身份識別的問題了。
完整示例:
apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx --- apiVersion: apps/v1beta1 kind: StatefulSet metadata: name: web spec: serviceName: "nginx" replicas: 1 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.11 ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html nodeSelector: node: kube-node3 volumes: - name: www hostPath: path: /mydir
使用StatefulSet部署有兩個需要特別注意:
創建headless的使用要指定 clusterIP: None
在創建pod的時候一定要指定headless中service的名字,spec:
serviceName: "nginx"
svc.cluster.local
start-foreground



