服務是一種爲一組相同功能的pod提供單一不變接入點的資源。當服務存在時,他的IP和端口不會改變。客戶端經過IP和端口創建鏈接,這些鏈接會被路由到任何一個pod上。如此,客戶端不須要知道每一個單獨提供服務的pod地址,這些pod也能夠隨時被建立、刪除。html
服務經過標籤選擇器決定選擇哪些pod。node
首先要準備一個可以提供web服務的鏡像,做者將鏡像存儲到了阿里雲的鏡像倉庫。linux
監聽8000端口,接到請求輸出當前hostnamenginx
package main import ( "fmt" "log" "net/http" "os" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { hostname,_ := os.Hostname(); fmt.Fprintf(w,"this is %v\n",hostname) }) log.Fatal(http.ListenAndServe(":8000",nil)) }
多步驟構建,有關與此以及Dockerfile的文章請見:Docker學習筆記(三):Dockerfile及多步驟構建鏡像git
FROM golang:1.14-alpine COPY goweb.go /src/ RUN CGO_ENABLED=0 GOOS=linux go build -o /bin/goweb /src/goweb.go FROM alpine COPY --from=0 /bin/goweb /usr/local/bin/ RUN apk add --no-cache curl EXPOSE 8000 CMD ["/usr/local/bin/goweb"]
構建完成github
-> [feifei@ffmac.local] [~/work/service] docker build -t registry.cn-hangzhou.aliyuncs.com/orzi/goweb . Sending build context to Docker daemon 3.072kB ...... Successfully built 3db4b643ba0a Successfully tagged registry.cn-hangzhou.aliyuncs.com/orzi/goweb:latest
運行鏡像,本地8001映射到容器的8000golang
-> [feifei@ffmac.local] [~] docker run --rm -d -p 8001:8000 registry.cn-hangzhou.aliyuncs.com/orzi/goweb 6241a412caeacfe5f025d20af154b2eba98555fcb2b2f55742154a9e6fa46817
請求本地的8001端口web
-> [feifei@ffmac.local] [~] curl http://localhost:8001 this is 6241a412caea
沒有問題,推送docker
-> [feifei@ffmac.local] [~] docker push registry.cn-hangzhou.aliyuncs.com/orzi/goweb The push refers to repository [registry.cn-hangzhou.aliyuncs.com/orzi/goweb] 8c5325633e9a: Pushed 3e207b409db3: Pushed latest: digest: sha256:2704d806836060237169ea59cfda238c50fc5e5881e15cb1230200b5c8b2f5a0 size: 739
apiVersion: apps/v1 kind: ReplicaSet metadata: name: goweb-rs spec: replicas: 2 selector: matchLabels: app: goweb template: metadata: labels: app: goweb spec: containers: - name: goweb image: registry.cn-hangzhou.aliyuncs.com/orzi/goweb ports: - containerPort: 8000
apiVersion: v1 kind: Service metadata: name: goweb-svc spec: ports: - port: 80 # 服務的端口 targetPort: 8000 # 服務將鏈接轉發到容器的端口 selector: # 匹配此選擇器的都屬於這個服務 app: goweb
建立一個名爲goweb-svc的服務,它將在80端口接收請求並將鏈接路由到具備標籤app=goweb的pod的8000端口上。api
-> [root@kube0.vm] [~] k create -f goweb-svc.yaml service/goweb-svc created -> [root@kube0.vm] [~] k create -f goweb-rs.yaml replicaset.apps/goweb-rs created -> [root@kube0.vm] [~] k get all NAME READY STATUS RESTARTS AGE pod/goweb-rs-6n6fw 1/1 Running 0 14m pod/goweb-rs-vkwqb 1/1 Running 0 14m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/goweb-svc ClusterIP 10.104.46.76 <none> 80/TCP 9s service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 40h NAME DESIRED CURRENT READY AGE replicaset.apps/goweb-rs 2 2 2 14m
訪問服務能夠在集羣節點上直接訪問,如:
-> [root@kube0.vm] [~] curl http://10.104.46.76 this is goweb-rs-vkwqb -> [root@kube0.vm] [~] curl http://10.104.46.76 this is goweb-rs-6n6fw
或者經過kubectl exec
命令在已存在的pod上執行curl
-> [root@kube0.vm] [~] k exec goweb-rs-vkwqb -- curl -s http://10.104.46.76 this is goweb-rs-vkwqb -> [root@kube0.vm] [~] k exec goweb-rs-vkwqb -- curl -s http://10.104.46.76 this is goweb-rs-6n6fw
雙橫槓表示kubectl命令項的結束,在此以後的是指要在pod內部執行的命令。這是爲了不異常和歧義,若是須要執行的指令中沒有橫槓,那麼能夠不用雙橫槓。
使用svc.spec.sessionAffinity
設置會話親和性,默認是None。指定爲ClientIP會使來自同一個Client IP的請求轉發到同一個Pod上。
Kubernetes只支持這兩種親和性設置,不支持cookie,由於Kubernetes不在HTTP層面工做。服務處理TCP和UDP包,不關心其中的內容。
建立一個多端口服務時,必須給每一個端口指定名字。
apiVersion: v1 kind: Service metadata: name: goweb-svc spec: ports: - name: http port: 80 targetPort: 8000 - name: https port: 443 targetPort: 8443 selector: app: goweb
使用方法是:在Pod(或其餘資源的Pod模板)的spec.containers.ports.name
配置中指定端口名稱,而後Service中的spec.ports.targetPort
引用。
使用命名端口的好處在於,當Pod更改端口號時,不會影響到Service,由於Service引用的是端口名。
Pod中指定端口名:
spec: containers: - name: goweb ports: - name: http # 命名8000端口爲http containerPort: 8000 - name: https # 命名8443端口爲https containerPort: 8443
Service中引用:
apiVersion: v1 kind: Service metadata: name: goweb-svc spec: ports: - name: http # 將80端口映射到容器中名爲http的端口 port: 80 targetPort: http - name: https # 將443端口映射到容器中名爲https的端口 port: 443 targetPort: https selector: app: goweb
在容器中執行env,列出一部分結果。
服務名稱中的橫線被轉換爲下劃線,而且所有轉爲大寫
-> [root@kube0.vm] [~] k exec goweb-rs-hztlt env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOSTNAME=goweb-rs-hztlt KUBERNETES_SERVICE_HOST=10.96.0.1 KUBERNETES_SERVICE_PORT=443 GOWEB_SVC_SERVICE_HOST=10.98.92.202 GOWEB_SVC_SERVICE_PORT=80 .........
Kubernetes集羣運行了一個名爲kube-dns的服務,提供DNS解析。
-> [root@kube0.vm] [~] k get svc -n kube-system NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 2d17h
查看容器內的/etc/resolv.conf
文件
-> [root@kube0.vm] [~] k exec goweb-rs-hztlt cat /etc/resolv.conf nameserver 10.96.0.10 search default.svc.cluster.local svc.cluster.local cluster.local lan vm options ndots:5
沒有錯,nameserver指定的IP就是kube-dns服務的地址!
FQDN(Fully Qualified Domain Name):全限定域名
好比:goweb-svc.default.svc.cluster.local,goweb-svc是Service名稱,default是命名空間,svc表示資源類型,cluster.local表示本地集羣后綴。
根據狀況,能夠省略命名空間(請求方與Service在相同命名空間下)和 svc.cluster.local,也就是說下列狀況都是能夠的:
-> [root@kube0.vm] [~] k exec goweb-rs-dknjv -- curl -s http://goweb-svc this is goweb-rs-dknjv -> [root@kube0.vm] [~] k exec goweb-rs-dknjv -- curl -s http://goweb-svc.default -> [root@kube0.vm] [~] k exec goweb-rs-dknjv -- curl -s http://goweb-svc.default.svc.cluster.local
由於服務的集羣IP是一個虛擬IP,而且只有在與服務端口結合時纔有意義。
Service與Pod不是直接相連的,有一種資源介於二者之間,他就是Endpoint。
Endpoint暴露一個服務的IP和端口列表。
查看Service
-> [root@kube0.vm] [~] k describe svc goweb-svc Name: goweb-svc Namespace: default ....... Endpoints: 10.244.1.58:8000,10.244.2.61:8000 .......
查看Endpoint資源
-> [root@kube0.vm] [~] k get ep NAME ENDPOINTS AGE goweb-svc 10.244.1.58:8000,10.244.2.61:8000 7h58m kubernetes 192.168.199.117:6443 8h
若是建立了不包含Pod選擇器的服務,Kubernetes將不會建立Endpoint資源。
external-service.yaml
apiVersion: v1 kind: Service metadata: name: external-svc spec: ports: - port: 80
查看external-svc
-> [root@kube0.vm] [~] k describe svc external-svc Name: external-svc Namespace: default Labels: <none> Annotations: <none> Selector: <none> Type: ClusterIP IP: 10.108.251.192 Port: <unset> 80/TCP TargetPort: 80/TCP Endpoints: <none> Session Affinity: None Events: <none>
Endpoint對象須要與服務具備相同的名字,幷包含該服務的目標IP和端口列表。
Service和Endpoint都提交到服務器後,Service就又能夠像具備Pod選擇器那樣了。
external-service-endpoint.yaml
apiVersion: v1 kind: Endpoints metadata: name: external-svc # 名字必須與相應服務的名字相同 subsets: - addresses: # IP、端口列表 - ip: 11.11.11.11 - ip: 22.22.22.22 ports: - port: 80
建立Endpoint
-> [root@kube0.vm] [~] k create -f external-svc-endpoint.yaml endpoints/external-svc created
查看external-svc服務
-> [root@kube0.vm] [~] k describe svc external-svc Name: external-svc Namespace: default Labels: <none> Annotations: <none> Selector: <none> Type: ClusterIP IP: 10.108.251.192 Port: <unset> 80/TCP TargetPort: 80/TCP Endpoints: 11.11.11.11:80,22.22.22.22:80 Session Affinity: None Events: <none>
建立一個具備別名的外部服務時,須要指定service.spec.type
的值爲ExternalName
;而且指定service.spec. externalName
的值爲外部服務的完整域名。所以鏈接到服務的客戶端將直接鏈接到外部服務,徹底繞過服務代理。出於這個緣由,這些類型的服務也就沒有集羣IP
external-svc-externalname.yaml
apiVersion: v1 kind: Service metadata: name: external-svc-externalname spec: type: ExternalName # 服務類型 externalName: www.baidu.com # 外部服務的完整域名 ports: - port: 80
建立、查看別名外部服務,能夠看到external-svc-externalname確實沒有集羣IP
-> [root@kube0.vm] [~] k create -f external-svc-externalname.yaml service/external-svc-externalname created -> [root@kube0.vm] [~] k get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE external-svc ClusterIP 10.108.251.192 <none> 80/TCP 57m external-svc-externalname ExternalName <none> www.baidu.com 80/TCP 18s kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 9h
經過external-svc-externalname.default.svc.cluster.local
甚至是external-svc-externalname
能夠訪問服務。
如下幾種方式可在外部訪問服務:
goweb-nodeport.yaml
apiVersion: v1 kind: Service metadata: name: goweb-nodeport spec: type: NodePort # Service類型 ports: - port: 80 # Service的端口 targetPort: 8000 # Pod的端口 nodePort: 31234 # 經過任意Node的此端口訪問服務 selector: app: goweb
建立、查看這個類型爲NodePort的服務
-> [root@kube0.vm] [~] k create -f goweb-nodeport.yaml service/goweb-nodeport created -> [root@kube0.vm] [~] k get svc goweb-nodeport NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE goweb-nodeport NodePort 10.105.234.226 <none> 80:31234/TCP 10s
查看節點IP
-> [root@kube0.vm] [~] k get node -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME kube0.vm Ready master 3d5h v1.17.3 192.168.199.117 <none> CentOS Linux 7 (Core) 5.6.14-1.el7.elrepo.x86_64 docker://19.3.6 kube1.vm Ready <none> 3d5h v1.17.3 192.168.199.231 <none> CentOS Linux 7 (Core) 5.6.14-1.el7.elrepo.x86_64 docker://19.3.6 kube2.vm Ready <none> 3d5h v1.17.3 192.168.199.212 <none> CentOS Linux 7 (Core) 5.6.14-1.el7.elrepo.x86_64 docker://19.3.6
使用不一樣節點IP訪問服務
-> [root@kube0.vm] [~] curl http://192.168.199.231:31234 this is goweb-rs-pbxvx -> [root@kube0.vm] [~] curl http://192.168.199.212:31234 this is goweb-rs-6wc2f
搞個圖
goweb-loadbalancer.yaml
apiVersion: v1 kind: Service metadata: name: goweb-loadbalancer spec: type: LoadBalancer # 更改了類型 ports: # 去掉了nodePort、隨機分配 - port: 80 targetPort: 8000 selector: app: goweb
建立、查看這個類型爲LoadBalancer的服務
-> [root@kube0.vm] [~] k create -f goweb-loadbalancer.yaml service/goweb-loadbalancer created -> [root@kube0.vm] [~] k get svc goweb-loadbalancer NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE goweb-loadbalancer LoadBalancer 10.106.251.246 <pending> 80:31141/TCP 21m
EXTERNAL-IP字段一直是<pending>
,是做者的環境不支持LoadBalancer。退而求其次,將其做爲NodePort使用:
-> [root@kube0.vm] [~] curl http://192.168.199.212:31141 this is goweb-rs-pbxvx -> [root@kube0.vm] [~] curl http://192.168.199.231:31141 this is goweb-rs-6wc2f
搞個圖
sservice.spec.externalTrafficPolicy
設置爲local
,則服務代理會選擇運行本地的Pod。若是本地Node沒有Pod,則鏈接將掛起。
節點端口接收到鏈接是,會對包的源地址進行轉換(SNAT),所以數據包的源IP將發生改變。
可是externalTrafficPolicy爲local的不會進行SNAT
由於每一個LoadBalancer服務都須要本身的負載均衡器,以及獨有的共有IP地址。而Ingress只須要一個地址就能夠爲多個服務提供訪問。當客戶端向Ingress發送Http請求時,Ingress會根據請求的主機名和路徑決定請求轉發到那個服務。
只有Ingress控制器在集羣中運行,Ingress資源才能正常工做。因此咱們要先部署Ingress控制器,須要作的工做很是簡單:
進入ingress-nginx官網,複製粘貼如下內容,而後執行就能夠了。
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-0.32.0/deploy/static/provider/baremetal/deploy.yaml
這時候查看Namespace,會發現多出一個ingress-nginx,具體內容就不細述了。
-> [root@kube0.vm] [~] k get ns NAME STATUS AGE default Active 3d15h ingress-nginx Active 15m kube-node-lease Active 3d15h kube-public Active 3d15h kube-system Active 3d15h
除此以外還要作個小改動,執行k edit -n ingress-nginx service/ingress-nginx-controller
,在spec下添加工做節點的IP。出處請見官網
externalIPs: - 192.168.199.231 - 192.168.199.212
而後刪除其管理的Pod,使其重建。
-> [root@kube0.vm] [~] k delete -n ingress-nginx pod/ingress-nginx-controller-f8d756996-8prk2 pod "ingress-nginx-controller-f8d756996-8prk2" deleted
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: goweb-ingress spec: rules: - host: www.goweb.com http: paths: - path: / backend: serviceName: goweb-svc servicePort: 80
建立、查看goweb-ingress
-> [root@kube0.vm] [~] k create -f goweb-ingress.yaml ingress.extensions/goweb-ingress created -> [root@kube0.vm] [~] k get ing NAME HOSTS ADDRESS PORTS AGE goweb-ingress www.goweb.com 192.168.199.212 80 61s
在host文件中添加 192.168.199.212 www.goweb.com
。而後訪問
-> [root@kube0.vm] [~] curl http://www.goweb.com this is goweb-rs-mhvj4 -> [root@kube0.vm] [~] curl http://www.goweb.com this is goweb-rs-88k9t
須要暴露多個服務,參照goweb-ingress.yaml:
pahts
下再配置一個path
便可。host
便可。從表面上看請求的流程是:client->ingress->service->pod,但其實是client->ingress->pod。
在Ingress收到客戶端的請求後,會根據域名和路徑來肯定服務,經過與該服務關聯的Endpoint對象查看Pod IP,並將客戶端的請求轉發給其中一個pod。也就是說,請求不會轉發給服務,服務只是被用來選擇pod。
佔個位置,之後補上。
就緒探針(readinessProbe)的類型與存活探針(livenessProbe)同樣,請見此文。它與存活探針的不一樣在於:若是容器未經過檢查,則不會被終止或從新啓動,但Pod會被從服務的Endpoint中移除,它確保了客戶端只與正常的Pod交互。
在衆多的微服務中存在不少依賴關係,被依賴服務只有在準備就緒後,才能接收請求。因此就緒探針務必要定義。
apiVersion: apps/v1 kind: ReplicaSet metadata: name: goweb-readiness spec: replicas: 2 selector: matchLabels: app: goweb template: metadata: labels: app: goweb spec: containers: - name: goweb image: registry.cn-hangzhou.aliyuncs.com/orzi/goweb ports: - name: http containerPort: 8000 readinessProbe: exec: command: ["ls","/var/ready"]
建立以後等了一段時間,能夠看到兩個Pod都處於爲就緒狀態,goweb-svc的Endpoints也是空的。
-> [root@kube0.vm] [~] k create -f goweb-readiness.yaml replicaset.apps/goweb-rs created -> [root@kube0.vm] [~] k get po NAME READY STATUS RESTARTS AGE goweb-readiness-9k9kv 0/1 Running 0 11s goweb-readiness-x2gfb 0/1 Running 0 11s -> [root@kube0.vm] [~] k describe svc goweb-svc Port: http 80/TCP TargetPort: http/TCP Endpoints: Session Affinity: None Events: <none>
讓咱們來給 goweb-readiness-9k9kv 建立一個/var/ready文件,使其準備就緒。
-> [root@kube0.vm] [~] k exec goweb-readiness-9k9kv touch /var/ready
再來查看、能夠看到已經有一個Pod就緒了,Endpoints也有內容了。
-> [root@kube0.vm] [~] k get po NAME READY STATUS RESTARTS AGE goweb-readiness-9k9kv 1/1 Running 0 89s goweb-readiness-x2gfb 0/1 Running 0 89s -> [root@kube0.vm] [~] k describe svc goweb-svc Endpoints: 10.244.2.74:8000
將服務的spec.clusterIP
設置爲None
會使服務成爲headless服務。它不會被分配集羣IP,DNS對其解析時就會返回Pod IP。
默認狀況下,DNS對headless服務名解析只會返回已經就緒的Pod的IP。
apiVersion: v1 kind: Service metadata: name: goweb-headless spec: clusterIP: None ports: - name: http port: 80 targetPort: 8000 selector: app: goweb
ReplicaSet是使用前面的goweb-readiness.yaml建立的。
-> [root@kube0.vm] [~] k create -f goweb-headless.yaml service/goweb-headless created -> [root@kube0.vm] [~] k get all -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod/goweb-readiness-2jj49 0/1 Running 0 10m 10.244.2.4 kube2.vm <none> <none> pod/goweb-readiness-5h5sg 0/1 Running 0 10m 10.244.1.5 kube1.vm <none> <none> NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR service/goweb-headless ClusterIP None <none> 80/TCP 9m54s app=goweb service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 79m <none> NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR replicaset.apps/goweb-readiness 2 2 1 10m goweb registry.cn-hangzhou.aliyuncs.com/orzi/goweb app=goweb
使用nslookup解析goweb-headless,沒有返回任何Pod的IP,這是由於兩個Pod都未就緒。
-> [root@kube0.vm] [~] k exec goweb-readiness-2jj49 nslookup goweb-headless Server: 10.96.0.10 Address: 10.96.0.10:53 ** server can't find goweb-headless.default.svc.cluster.local: NXDOMAIN ** server can't find goweb-headless.svc.cluster.local: NXDOMAIN ........
爲goweb-readiness-2jj49建立/var/ready使其知足就緒條件
k exec goweb-readiness-2jj49 touch /var/ready
再使用nslookup查看,能夠看到返回了goweb-readiness-2jj49 的 IP
-> [root@kube0.vm] [~] k exec goweb-readiness-2jj49 nslookup goweb-headless Server: 10.96.0.10 Address: 10.96.0.10:53 ...... Name: goweb-headless.default.svc.cluster.local Address: 10.244.2.4 ......
如法炮製,使另外一個Pod生效,就能看到兩個Pod的IP了
將service.spec.publishNotReadyAddresses
設置爲true
,容許DNS解析headless服務是發現未就緒的Pod。
將goweb-readiness的Pod副本變成3個
-> [root@kube0.vm] [~] k scale --replicas=3 rs goweb-readiness replicaset.apps/goweb-readiness scaled -> [root@kube0.vm] [~] k get po -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES goweb-readiness-2jj49 1/1 Running 0 24m 10.244.2.4 kube2.vm <none> <none> goweb-readiness-5h5sg 1/1 Running 0 24m 10.244.1.5 kube1.vm <none> <none> goweb-readiness-jb74c 0/1 Running 0 31s 10.244.1.6 kube1.vm <none> <none>
而後編輯goweb-headless,寫入publishNotReadyAddresses: true
。
-> [root@kube0.vm] [~] k edit svc goweb-headless service/goweb-headless edited
nslookup查看,雖然有未就緒的,但IP仍是全都返回了。
-> [root@kube0.vm] [~] k exec goweb-readiness-2jj49 nslookup goweb-headless Server: 10.96.0.10 Address: 10.96.0.10:53 Name: goweb-headless.default.svc.cluster.local Address: 10.244.2.4 Name: goweb-headless.default.svc.cluster.local Address: 10.244.1.5 Name: goweb-headless.default.svc.cluster.local Address: 10.244.1.6
sservice.spec.sessionAffinity
設置會話親和性,默認爲None,能夠設置爲ClientIPspec.type
設置爲ExternalName
,此類服務只在DNS級別實施(爲服務建立了CNAME DNS),也所以不會得到集羣IP。service.spec.externalTrafficPolicy
設置爲local
,則服務代理會選擇運行本地的Pod。若是本地Node沒有Pod,則鏈接將掛起。service.spec.publishNotReadyAddresses
設置爲true
,容許DNS解析headless服務是發現未就緒的Pod。