大数据平台迁移至K8S环境(以HDFS为例)

Kubernetes 由 Google 推出,是 Google 十几年以来大规模应用容器技术的经验积累。Kubernetes 具有完备的集群管理能力,包括多层次的安全防护和准入机制、多租户应用支撑能力、透明的服务注册和服务发现机制、内建智能负载均衡器、强大的故障发现和自我修复能力、服务滚动升级和在线扩容能力、可扩展的资源自动调度机制,以及多粒度的资源配额管理能力。

本次以 HDFS 为例,将 HDFS 迁移到 K8S 环境下。

1. Statefulset

Statefulset 是kubernetes 中资源对象的一种,它是为了解决有状态服务的问题(对应Deployments和ReplicaSets是为无状态服务而设计),其应用场景包括:

  • 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
  • 稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现
  • 有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现
  • 有序收缩,有序删除(即从N-1到0)

应用采用 StatefulSet 方式部署后,DNS 解析的时候是可以解析到 Pod。StatefulSet 中每个 Pod 的 DNS 格式为 statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local,其中:

  • serviceName 为 Headless Service 的名字
  • 0..N-1为 Pod 所在的序号,从0开始到N-1
  • statefulSetName 为 StatefulSet 的名字
  • namespace 为服务所在的 namespace,Headless Servic 和 StatefulSet必须在相同的namespace
  • .cluster.local 为 Cluster Domain

2. Headless Service

Headless Service 是没有 ClusterIP 的 Service,StatefulSet 必须与 Headless Service 配合使用。

3. 部署HDFS

因为 namenode 和 datanode 之间必须要有稳定的网络标识,因此采用StatefulSet的方式进行部署。

(1) namenode

namenode 的 yaml 文件如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

apiVersion: v1
kind: Service
metadata:
name: namenode
spec:
ports:
- port: 50070
targetPort: 50070
clusterIP: None
selector:
app: namenode
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: namenode-ss
spec:
replicas: 1
serviceName: "namenode"
selector:
matchLabels:
app: namenode
template:
metadata:
labels:
app: namenode
spec:
nodeSelector:
harole: slave4
containers:
- name: namenode-con
#imagePullPolicy: IfNotPresent
image: 10.0.3.6:5000/bigdata/namenode:2.6.5_1
ports:
- containerPort: 50070
env:
- name: CORE_CONF_fs_defaultFS
value: "hdfs://namenode:9000"
- name: YARN_CONF_yarn_resourcemanager_hostname
value: "resourcemanager"
volumeMounts:
- name: namenode-volume
mountPath: /home1/hadoop_tmp
volumes:
- name: namenode-volume
hostPath:
path: /home/panfan/k8s_namenode

(2) datanode

datanode 的 yaml 文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
apiVersion: v1
kind: Service
metadata:
name: datanode
spec:
ports:
- port: 22
targetPort: 22
clusterIP: None
selector:
app: datanode
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: datanode1-ss
spec:
replicas: 1
serviceName: "datanode"
selector:
matchLabels:
app: datanode
template:
metadata:
labels:
app: datanode
spec:
nodeSelector:
harole: slave60
containers:
- name: datanode1-con
imagePullPolicy: IfNotPresent
image: 10.0.3.6:5000/bigdata/datanode:2.6.5_1
ports:
- containerPort: 22
env:
- name: CORE_CONF_fs_defaultFS
value: "hdfs://namenode:9000"
- name: YARN_CONF_yarn_resourcemanager_hostname
value: "resourcemanager"
volumeMounts:
- name: datanode1-volume
mountPath: /datanode
volumes:
- name: datanode1-volume
hostPath:
path: /home/panfan/k8s_datanode

---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: datanode2-ss
spec:
replicas: 1
serviceName: "datanode"
selector:
matchLabels:
app: datanode
template:
metadata:
labels:
app: datanode
spec:
nodeSelector:
harole: slave4
containers:
- name: datanode2-con
imagePullPolicy: IfNotPresent
image: 10.0.3.6:5000/bigdata/datanode:2.6.5
ports:
- containerPort: 22
env:
- name: CORE_CONF_fs_defaultFS
value: "hdfs://namenode:9000"
- name: YARN_CONF_yarn_resourcemanager_hostname
value: "resourcemanager"
volumeMounts:
- name: datanode2-volume
mountPath: /datanode
volumes:
- name: datanode2-volume
hostPath:
path: /home/panfan/k8s_datanode

(3) 一些说明

  • 持久化存储:对于持久化存储,一般采用PV和PVC实现。k8s 目前支持多种 PV 类型,如 NFS、CephFS、GlusterFS。 这个地方,我没考虑使用这些后端存储,因为HDFS本身也是一种分布式文件系统,现在的做法相当于是干涉K8S对POD的调度,指定每个datanode调度到哪些物理节点上,然后用hostPath的方式将需要持久化的数据挂载到这些物理节点上。

  • daemonset: 是K8S在v1.2版本新增的一种资源对象,可以保证在集群的每个Node节点上仅运行一个POD副本实例。之前也试过采用这种方式 ,但是报错,为什么呢? 因为daemonset这种方式下部署的POD没有稳定的网络标识,它不是Statefulset,这样namenode 会报错无法解析 datanode 地址。如果要试一下daemonset 这种方式来部署datanode,可以尝试一下将datanode的network改为host模式。

  • 外界访问: 因为是采用Headless Service进行部署的,这种 Service 没有Cluster IP,那么对于外部而言,要如何访问我们这里的 HDFS 服务呢?很简单,重新写一个普通的service,该service要能够筛选出刚才我们的namenode,通过该service的nodePort将namenode的服务出来。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: namenode-svc
spec:
type: NodePort
ports:
- port: 50070
targetPort: 50070
nodePort: 30003
selector:
app: namenode