文件同步
背景
Spark的 driver template,executor template,在跨 k8s 的 namespace 下,需要再搞一份
比如 executor需要这些
1
2
3
4
5
6
7
|
volumeMounts:
- mountPath: /tmp/krb5_conf
name: my_project-krb5-conf
- mountPath: /tmp/krb5_keytab
name: my_project-krb5-keytab
- mountPath: /my_project/resources/kerberos
name: keytab-config
|
driver的就更多了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
volumeMounts:
- mountPath: /tmp/krb5
name: kerberos-temp
- mountPath: /my_project/resources/kerberos
name: keytab-config
- mountPath: /tmp/executoe_template
name: my_project-executor-template
- mountPath: /my_project/resources
name: resource-config
- mountPath: /tmp/application-properties
name: my_project-app-properties
- mountPath: /my_project/resources/config
name: app-config
- mountPath: /tmp/scheduler-config
name: my_project-scheduler-config
- mountPath: /my_project/resources/scheduler-config
name: scheduler-config
- mountPath: /tmp/ranger
name: my_project-ranger-xml
|
在资源隔离场景下,使用了 k8s 的 namespace,由于 namespace是天然隔离的,所以 不同 namespace 中的 配置文件没法共用
如上,本来有一个 namespace-1,下面有若干文件,这时候,基于 namespace 1 启动了一个新的 pod,是放在 namespace-2下的,但如果要正常使用这个 pod,就需要在 namespace 2 下把相关文件重新创建一遍
希望达成的效果
- proxy 创建 engine时,不需要再创建配置文件了,可以自动完成
- 跨 k8s 的 namespace 下创建 engine时,也不需要冗余手动创建配置文件了,可以自动完成
实现方案
目前想到了有这么一些方案
- 基于 ZK 同步配置文件
- 基于 minio 同步配置文件
- 基于 HDFS 同步配置文件
- 内置一个 http,其他 pod主动拉取
- pod 之间使用组播实现同步
- 基于环境变量同步
基于ZK、minio、HDFS的同步
这几种本质是差不多的,都是把配置文件放到一个外部组件中
这些依赖都有一些问题
- HDFS 是最麻烦的一个,因为使用 HDFS,得先有 krb5 文件,但此时还没有这个文件
- minio需要密码,可能需要通过 env 先传递过去
- zk 目前只需要 ip 和port可以直接获取,但如果环境要求必须用 kerberos 连接,那也不好搞了
基于 http同步,组播
proxy 自身启动一个 http 服务,然后 engine 主动去连接这个 http
但 proxy 如果宕机了,engine 就不知道去哪里获取了
使用组播的方式,大家都先加入一个组播,然后识别出各自的身份
这样即使proxy挂了,新的proxy如果能加入这个组,engine还是可以找到的
实现组播也有一些问题
- 所有的应用都是基于 k8s的,那就相当于每个应用一个组播,如何确保组播地址不冲突,相互不会干扰
- 几个proxy + 几百个engine时,如何平衡读取 proxy,以及很多细节需要处理
基于环境变量
这个比较简单,直接将配置文件的内容写到环境变量中
新 pod 启动后,直接读取环境变量的值,然后将值的内容 写入到指定的路径中
比如直接将 环境变量中的值,写入到 /my_project/resources/config/application.properties
之后的逻辑就按正常方式读取 application.properties 即可
实现细节
拉取配置文件(不管是基于ZK、或是ENV),这个动作本身的需要在正常逻辑启动之前执行
因为正常的逻辑需要读取配置文件,krb5等,如果拉取动作跟正常逻辑混在一起了,可能还没拉取就报错了
同时拉取这个动作,跟正常逻辑不应该耦合太紧,所以放到 init 容器里面做这些事情比较合适
一个 namespace 内的拉取流程
- proxy 需要配置出 engine 的init 容器
- 之后再创建出一个 volume,这个 volume 跟 pod 生命周期一样即可,pod 销毁了配置文件也就没了
- proxy 读取本地,也就是容器内的配置文件,作为 env传递给 engine 的 init 容器
- init 容器读取解析出这些 env,保存到 共享的 volume 中
- engine 的正常逻辑继续正常,此时配置文件都被 init 容器正常放置好了,engine后续的逻辑不需动,正常执行
跨 namespace 的拉取流程
- 这个流程总体上跟 namespace内的 是差不多
- proxy 也是要创建出 init容器、volume,只是发布到 不同的namespace中
- 新namespace 下的 engine会继续创建出 executor,他们两个是在同一个 namespace 下的
- engine 也会创建出 executor 的 init 容器、volume
- 之后把 krb5的两个文件传递到 init 容器的 env中,init 读取写入到共享的 volume中
- 之后 executor 就可以读取到这些文件,后面走正常逻辑可
操作步骤
增加配置:
spark.kubernetes.driver.pod.featureSteps=org.apache.spark.server.k8s.extension.EngineSetup,org.apache.spark.server.k8s.extension.EngineNamespaceFeature
spark.kubernetes.executor.pod.featureSteps=org.apache.spark.server.k8s.extension.ExecutorSetup
启动后,查看 pod 描述,会有一个 init 容器,这个是自动创建的
其中 cmd 命令是java -cp .:/xxxx.jar com.test.XXX
这个类是在 init容器中,启动一个拉取 ENV 的逻辑
env 的name 是文件名,value是文件内容的base64编码
然后是 mount,这个也是自动生成的
最后增加 host-aliase,proxy拿到自己的 host-aliase,然后直接设置到 engine 的pod 上
进入 engine pod,host 就被自动设置上了
镜像统一
背景
目前项目包含了两个镜像,一个 实际业务 镜像,一个是 sparkexecutor 镜像
在流水线部署 的时候,源码实际是被编译了两次
一次是编译打包生成 实际业务 镜像
一次是编译打包 将文件拷贝至 spark-executor 镜像
两次编译导致每次部署时间都很长
在实际项目部署时也需要管理两个镜像
因上述原因,现将 两个镜像 合二为一,统一成一个镜像
实现
将两个镜像合并为一个,大致有三种策略
- 将原基础镜像(OpenJDK11),跟 spark-executor镜像整合,变成新的基础镜像,项目编译代码后,将编译后的文件直接拷贝到整合的镜像中
- 原基础镜像不动,打包时,将spark-executor镜像中的内容拷贝到基础镜像中,将项目的编译后的文件也拷贝到基础镜像中
- 将原基础镜像的内容拷贝到 spark-executor镜像中,之后项目编译的文件也拷贝到 spark-executor中
第一种方式,后续Spark 升级的话维护起来会麻烦一点,好处是流水线打包速度会更快
第二种方式,维护起来会简单一些,基础镜像和spark-executor可以单独升级,但部署流水线会慢 2-3 分钟
第三种方式,spark-executor镜像使用的是ubuntu,有些工具不支持,另外还需要冗余的拷贝一份JDK
目前使用的是 第二种方式
镜像合并后,需要一种机制来判断,是启动 engine,还是 executor
目前的做法是,如果环境变量中不包含 MY_PROJECT_EXECUTOR,则认为当前角色是 engine,启动 engine
否则,启动 executor
当 engine启动 executor之前,会执行一个 k8s扩展,将 MY_PROJECT_EXECUTOR 变量设置进去,于是新 pod 启动时就认为自己是 executor
dockerfile
统一镜像后的dockerfile
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
|
# builder
FROM maven:3.6.3-openjdk-11-slim AS builder
COPY . .
COPY settings.xml $MAVEN_CONFIG/
WORKDIR /
RUN mvn clean package -Dmaven.test.skip=true
# 使用 Spark3.3.2 的镜像,后续升级的话,将这里的版本号修改一下即可
FROM spark:3.3.2-amd64 AS SPARK_IMAGE
# 基础镜像,包含 JDK11.0.16,以及一些方便排查的工具和命令
FROM base:OpenJDK-11.0.16_V1.0
ARG TARGET=/my_project-main/target
ARG MY_PROJECT=/my_project
ARG DEPENDENCY=/dependency
ARG SPARK_HOME=/opt/spark
ARG JAVA_HOME=/usr/local/openjdk-11.0.16+8-x64
ENV SPARK_HOME=$SPARK_HOME
ENV MY_PROJECT_DOCKER_IMAGE=my_project.container.image
ENV SPARK_CLASSPATH $SPARK_CLASSPATH:$SPARK_HOME/lib/*
# 将 Spark 镜像中的目录、相关文件、核心启动脚本拷贝到 基础镜像中
COPY --from=SPARK_IMAGE /opt/spark /opt/spark
COPY --from=SPARK_IMAGE /opt/decom.sh /opt
COPY --from=SPARK_IMAGE /opt/entrypoint.sh /opt
COPY --from=SPARK_IMAGE /usr/bin/tini /usr/bin
COPY --from=builder $DEPENDENCY/bootstrap.sh /opt
COPY --from=builder $TARGET/main-*.jar $MY_PROJECT/engine-main.jar
COPY --from=builder $TARGET/lib $MY_PROJECT/lib
COPY --from=builder $TARGET/lib $SPARK_HOME/lib
COPY --from=builder $TARGET/main-*.jar $SPARK_HOME/lib
COPY --from=builder $TARGET/resources/scheduler-config $MY_PROJECT/resources/scheduler-config
COPY --from=builder $TARGET/resources/custom-log4j2-json-layout.json $MY_PROJECT/resources/custom-log4j2-json-layout.json
COPY --from=builder $TARGET/resources/log4j2-spring.xml $MY_PROJECT/resources/log4j2-spring.xml
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo 'Asia/Shanghai' > /etc/timezone && \
useradd -ms /bin/bash hive && \
chmod +x /opt/bootstrap.sh
EXPOSE 8011
EXPOSE 4040
EXPOSE 7078
EXPOSE 7079
WORKDIR /my_project
# 统一镜像后的核心启动脚本
# 如果环境变量中包含 MY_PROJECT_EXECUTOR,则脚本调用 executor 的启动逻辑
# 否则,调用启动 my_project 的启动逻辑
ENTRYPOINT [ "/opt/bootstrap.sh" ]
|
基础镜像 dockerfile
基于 JDK11 打的基础镜像dockerfile
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
|
# 基于 CentOS 7.4 基础上,创建新基础镜像
# 此基础镜像包含 OpenJDK 11.0.16,和 Spark-executor 镜像的 JDK 版本保持一致
# 并增加了一些排查工具和命令
FROM centos:centos7.4.1708
LABEL describe="MY_PROJECT base image(OpenJDK 11.0.16)"
RUN rm -f /etc/yum.repos.d/*
COPY My_project-Centos7-public.repo /etc/yum.repos.d/
COPY openjdk-11.0.16+8-x64 /usr/local/openjdk-11.0.16+8-x64
COPY arthas /arthas
RUN yum -y install krb5-devel krb5-workstation && \
yum -y install curl && \
yum -y install dstat && \
yum -y install traceroute && \
yum -y install wget && \
yum -y install lsof && \
yum -y install tcpdump && \
yum -y install net-tools && \
yum -y install sysstat && \
yum -y install telnet && \
yum -y install psmisc && \
echo "alias ll='ls -l'" >> /root/.bashrc && \
yum -y remove perl && \
rm -rf /var/cache/yum && \
touch /var/cache/yum
ENV PATH=${PATH}:/usr/local/openjdk-11.0.16+8-x64/bin
ENV TZ=Asia/Shanghai
ENV LANG zh_CN.UTF-8
|