# 如何訪問K8S中的服務

# Ingress 介紹
Kubernetes 暴露服務的方式目前只有三種:LoadBlancer Service、NodePort Service、Ingress;前兩種估計都應該很熟悉,具體的可以參考下 [這篇文](https://mritd.me/2016/12/06/try-traefik-on-kubernetes//)章;下面詳細的嘮一下這個 Ingress
# Ingress 是個什么玩意
可能從大致印象上 Ingress 就是能利用 Nginx、Haproxy 啥的負載均衡器暴露集群內服務的工具;那么問題來了,集群內服務想要暴露出去面臨著幾個問題:
- #### Pod 漂移問題
眾所周知 Kubernetes 具有強大的副本控制能力,能保證在任意副本(Pod)掛掉時自動從其他機器啟動一個新的,還可以動態擴容等,總之一句話,這個 Pod 可能在任何時刻出現在任何節點上,也可能在任何時刻死在任何節點上;那么自然隨著 Pod 的創建和銷毀,Pod IP 肯定會動態變化;那么如何把這個動態的 Pod IP 暴露出去?這里借助于 Kubernetes 的 Service 機制,Service 可以以標簽的形式選定一組帶有指定標簽的 Pod,并監控和自動負載他們的 Pod IP,那么我們向外暴露只暴露 Service IP 就行了;這就是 NodePort 模式:即在每個節點上開起一個端口,然后轉發到內部 Service IP 上,如下圖所示

- #### 端口管理問題
采用 NodePort 方式暴露服務面臨一個坑爹的問題是,服務一旦多起來,NodePort 在每個節點上開啟的端口會及其龐大,而且難以維護;這時候引出的思考問題是 “能不能使用 Nginx 啥的只監聽一個端口,比如 80,然后按照域名向后轉發?” 這思路很好,簡單的實現就是使用 DaemonSet 在每個 node 上監聽 80,然后寫好規則,因為 Nginx 外面綁定了宿主機 80 端口(就像 NodePort),本身又在集群內,那么向后直接轉發到相應 Service IP 就行了,如下圖所示

- #### 域名分配及動態更新問題
從上面的思路,采用 Nginx 似乎已經解決了問題,但是其實這里面有一個很大缺陷:每次有新服務加入怎么改 Nginx 配置?總不能手動改或者來個 Rolling Update 前端 Nginx Pod 吧?這時候 “偉大而又正直勇敢的” Ingress 登場,如果不算上面的 Nginx,Ingress 只有兩大組件:Ingress Controller 和 Ingress
Ingress 這個玩意,簡單的理解就是 你原來要改 Nginx 配置,然后配置各種域名對應哪個 Service,現在把這個動作抽象出來,變成一個 Ingress 對象,你可以用 yml 創建,每次不要去改 Nginx 了,直接改 yml 然后創建/更新就行了;那么問題來了:”Nginx 咋整?”
Ingress Controller 這東西就是解決 “Nginx 咋整” 的;Ingress Controoler 通過與 Kubernetes API 交互,動態的去感知集群中 Ingress 規則變化,然后讀取他,按照他自己模板生成一段 Nginx 配置,再寫到 Nginx Pod 里,最后 reload 一下,工作流程如下圖

當然在實際應用中,最新版本 Kubernetes 已經將 Nginx 與 Ingress Controller 合并為一個組件,所以 Nginx 無需單獨部署,只需要部署 Ingress Controller 即可。
# 懟一個 Nginx Ingress
上面啰嗦了那么多,只是為了講明白 Ingress 的各種理論概念,下面實際部署很簡單
### 配置 ingress RBAC
cat nginx-ingress-controller-rbac.yml
#apiVersion: v1
#kind: Namespace
#metadata:
# name: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nginx-ingress-serviceaccount
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: nginx-ingress-clusterrole
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- "extensions"
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- "extensions"
resources:
- ingresses/status
verbs:
- update
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
name: nginx-ingress-role
namespace: kube-system
rules:
- apiGroups:
- ""
resources:
- configmaps
- pods
- secrets
- namespaces
verbs:
- get
- apiGroups:
- ""
resources:
- configmaps
resourceNames:
# Defaults to "<election-id>-<ingress-class>"
# Here: "<ingress-controller-leader>-<nginx>"
# This has to be adapted if you change either parameter
# when launching the nginx-ingress-controller.
- "ingress-controller-leader-nginx"
verbs:
- get
- update
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get
- create
- update
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: nginx-ingress-role-nisa-binding
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: nginx-ingress-role
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: nginx-ingress-clusterrole-nisa-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: nginx-ingress-clusterrole
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: kube-system
### 部署默認后端
我們知道 前端的 Nginx 最終要負載到后端 service 上,那么如果訪問不存在的域名咋整?官方給出的建議是部署一個 默認后端,對于未知請求全部負載到這個默認后端上;這個后端啥也不干,就是返回 404,部署如下
kubectl create -f default-backend.yaml
這個 default-backend.yaml 文件可以在 github [Ingress 倉庫](https://github.com/kubernetes/ingress-nginx/tree/master) 找到. 針對官方配置 我們單獨添加了 nodeselector 指定,綁定LB地址 以方便DNS 做解析。
cat default-backend.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: default-http-backend
labels:
k8s-app: default-http-backend
namespace: kube-system
spec:
replicas: 1
template:
metadata:
labels:
k8s-app: default-http-backend
spec:
terminationGracePeriodSeconds: 60
containers:
- name: default-http-backend
# Any image is permissable as long as:
# 1. It serves a 404 page at /
# 2. It serves 200 on a /healthz endpoint
image: harbor-demo.dianrong.com/kubernetes/defaultbackend:1.0
livenessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 30
timeoutSeconds: 5
ports:
- containerPort: 8080
resources:
limits:
cpu: 10m
memory: 20Mi
requests:
cpu: 10m
memory: 20Mi
nodeSelector:
kubernetes.io/hostname: 172.16.200.209
---
apiVersion: v1
kind: Service
metadata:
name: default-http-backend
namespace: kube-system
labels:
k8s-app: default-http-backend
spec:
ports:
- port: 80
targetPort: 8080
selector:
k8s-app: default-http-backend
### 部署 Ingress Controller
部署完了后端就得把最重要的組件 Nginx+Ingres Controller(官方統一稱為 Ingress Controller) 部署上
kubectl create -f nginx-ingress-controller.yaml
注意: 官方的 Ingress Controller 有個坑,默認注釋了hostNetwork 工作方式。以防止端口的在宿主機的沖突。沒有綁定到宿主機 80 端口,也就是說前端 Nginx 沒有監聽宿主機 80 端口(這還玩個卵啊);所以需要把配置搞下來自己加一下 hostNetwork
cat nginx-ingress-controller.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-ingress-controller
labels:
k8s-app: nginx-ingress-controller
namespace: kube-system
spec:
replicas: 1
template:
metadata:
labels:
k8s-app: nginx-ingress-controller
spec:
# hostNetwork makes it possible to use ipv6 and to preserve the source IP correctly regardless of docker configuration
# however, it is not a hard dependency of the nginx-ingress-controller itself and it may cause issues if port 10254 already is taken on the host
# that said, since hostPort is broken on CNI (https://github.com/kubernetes/kubernetes/issues/31307) we have to use hostNetwork where CNI is used
# like with kubeadm
# hostNetwork: true
terminationGracePeriodSeconds: 60
hostNetwork: true
serviceAccountName: nginx-ingress-serviceaccount
containers:
- image: harbor-demo.dianrong.com/kubernetes/nginx-ingress-controller:0.9.0-beta.1
name: nginx-ingress-controller
readinessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
livenessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
timeoutSeconds: 1
ports:
- containerPort: 80
hostPort: 80
- containerPort: 443
hostPort: 443
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
args:
- /nginx-ingress-controller
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
# - --default-ssl-certificate=$(POD_NAMESPACE)/ingress-secret
nodeSelector:
kubernetes.io/hostname: 172.16.200.102
### 部署 Ingress
從上面可以知道 Ingress 就是個規則,指定哪個域名轉發到哪個 Service,所以說首先我們得有個 Service,當然 Service 去哪找這里就不管了;這里默認為已經有了兩個可用的 Service,以下以 Dashboard 為例
先寫一個 Ingress 文件,語法格式啥的請參考 官方文檔,由于我的 Dashboard 都在kube-system 這個命名空間,所以要指定 namespace.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: dashboard-ingress
namespace: kube-system
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: fox-dashboard.dianrong.com
http:
paths:
- backend:
serviceName: kubernetes-dashboard
servicePort: 80
裝逼成功截圖如下

### 部署 Ingress TLS
上面已經搞定了 Ingress,下面就順便把 TLS 懟上;官方給出的樣例很簡單,大致步驟就兩步:創建一個含有證書的 secret、在 Ingress 開啟證書;但是我不得不噴一下,文檔就提那么一嘴,大坑一堆,比如多域名配置,TLS功能的啟動都沒。啟用tls 需要在 nginx-ingress-controller添加參數,上面的controller以配置好。
--default-ssl-certificate=$(POD_NAMESPACE)/ingress-secret
### 證書格式轉換
創建secret 需要使用你的證書文件,官方要求證書的編碼需要使用base64。轉換方法如下:
證書轉換pem 格式:
openssl x509 -inform DER -in cert/doamin.crt -outform PEM -out cert/domain.pem
證書編碼轉換base64
cat domain.crt | base64 > domain.crt.base64
### 創建 secret ,需要使用base64 編碼格式證書。
cat ingress-secret.yml
apiVersion: v1
data:
tls.crt: LS0tLS1CRU
tls.key: LS0tLS1CRU
kind: Secret
metadata:
name: ingress-secret
namespace: kube-system
type: Opaque
其實這個配置比如證書轉碼啥的沒必要手動去做,可以直接使用下面的命令創建,kubectl 將自動為我們完整格式的轉換。
kubectl create secret tls ingress-secret --key certs/ttlinux.com.cn-key.pem --cert certs/ttlinux.com.cn.pem
### 重新部署 Ingress
生成完成后需要在 Ingress 中開啟 TLS,Ingress 修改后如下
cat dashboard-ingress.yml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: dashboard-ingress
namespace: kube-system
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- hosts:
- fox-dashboard.dianrong.com
secretName: ingress-secret
rules:
- host: fox-dashboard.dianrong.com
http:
paths:
- backend:
serviceName: kubernetes-dashboard
servicePort: 80
注意:一個 Ingress 只能使用一個 secret(secretName 段只能有一個),也就是說只能用一個證書,更直白的說就是如果你在一個 Ingress 中配置了多個域名,那么使用 TLS 的話必須保證證書支持該 Ingress 下所有域名;并且這個 secretName 一定要放在上面域名列表最后位置,否則會報錯 did not find expected key 無法創建;同時上面的 hosts 段下域名必須跟下面的 rules 中完全匹配
更需要注意一點:之所以這里單獨開一段就是因為有大坑;Kubernetes Ingress 默認情況下,當你不配置證書時,會默認給你一個 TLS 證書的,也就是說你 Ingress 中配置錯了,比如寫了2個 secretName、或者 hosts 段中缺了某個域名,那么對于寫了多個 secretName 的情況,所有域名全會走默認證書;對于 hosts 缺了某個域名的情況,缺失的域名將會走默認證書,部署時一定要驗證一下證書,不能 “有了就行”;更新 Ingress 證書可能需要等一段時間才會生效
最后重新部署一下即可
kubectl delete -f dashboard-ingress.yml
kubectl create -f dashboard-ingress.yml
部署 TLS 后 80 端口會自動重定向到 443,最終訪問截圖如下

### ingress 高級用法

- lvs 反向代理到 物理nginx。完成https拆包,繼承nginx所有功能
- nginx 反向代理到ingress-control。 ingress-control 有兩種部署方式 。
- ingress-control 使用nodePort 方式暴漏服務
- ingress-control 使用hostNetwork 方式暴漏服務
#### 總結分析
1. ingress-control 在自己的所屬的namespace=ingress, 是可以夸不同namespace提供反向代理服.
1. 如果需要提供夸NS 訪問ingress,先給 ingress-control創建RBAC .
1. ingress-control 使用hostnetwork 模式 性能比使用service nodePort 性能好很多。因為hostnetwork 是直接獲取pod 的IP?

- 第一章 kubernetes 功能介紹
- 第二章 在CentOS上部署kubernetes1.7.6集群
- 第三章 創建TLS證書和秘鑰
- 第四章 安裝kubectl命令行工具
- 第五章 創建kubeconfig 文件
- 第六章 etcd 集群部署
- 第七章 部署k8s-master-v1.7.6節點
- 第八章 部署k8s-v1.7.6 node 節點
- 第九章 kubectl 操作示例
- 第十章 在kubernetes 部署第一個應用
- 第十一章 kubernetes之pod 調度
- 第十二章 K8S服務組件之kube-dns&Dashboard
- 第十三章 Kubernetes中的角色訪問控制機制(RBAC)支持
- 第十四章 部署nginx ingress
- 第十五章 使用Prometheus監控Kubernetes集群和應用
- 第十六章 使用helm 應用部署工具
- 第十七章 kubernetes 從1.7 到1.8升級記錄
- 第十八章 在kubernetes 使用ceph
- 第十九章 基于 Jenkins 的 CI/CD(一)
- 第二十章 基于jenkins的CI/CD(二)
- 第二十一章 基于prometheus自定指標HPA彈性伸縮