问题背景:
本篇文章主要探讨如何在 Kubernetes(K8s)环境下使用 Apache Flink 进行 Hive on CDH 的 Kerberos 认证。
flink在K8s环境下与Hive ON CDH 的kerberos认证
flink任务提交模式:kubernetes-application
k8s构建镜像模式:dockfile pod-template
准备工作:
1.示例代码:
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.common.serialization.DeserializationSchema;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.api.connector.source.Source;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.connector.kafka.source.KafkaSource;
import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.SqlDialect;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
import org.apache.flink.table.catalog.Catalog;
import org.apache.flink.table.catalog.hive.HiveCatalog;
import org.apache.flink.util.Collector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
public class Kafka2Hive {
public static final Logger log = LoggerFactory.getLogger(Kafka2Hive.class);
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
checkPointConfig(env, "kafka2hive");
StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
Configuration configuration = tableEnv.getConfig().getConfiguration();
configuration.setString("hive.exec.dynamic.partition", "true");
configuration.setString("hive.exec.dynamic.partition.mode", "nonstrict");
configuration.setString("hive.exec.dynamic.partitions", "10000");
SingleOutputStreamOperator<String> source = env.fromSource((Source) getKafkaSource(new String[]{Constants.KAFKA_TOPIC}, "gid_it.k8s.hive"), WatermarkStrategy.noWatermarks(), "kafka-source-it").uid("it.").name("Kafka Source: it.");
source.print();
SingleOutputStreamOperator<SupplierPartsInspection> javaBeanDs = source.flatMap(new FlatMapFunction<String, SupplierPartsInspection>() {
private SupplierPartsInspection supplierPartsInspection;
public void flatMap(String value, Collector<SupplierPartsInspection> out) throws Exception {
try {
JSONObject jsonObject = JSONObject.parseObject(value);
JSONArray content = jsonObject.getJSONArray("content");
this.supplierPartsInspection = new SupplierPartsInspection();
this.supplierPartsInspection.setReportTime(jsonObject.getTimestamp("reportTime"));
this.supplierPartsInspection.setReportInspectStatus(jsonObject.getString("reportInspectStatus"));
//数据异常监测信息
this.supplierPartsInspection.setCheckErrorType(jsonObject.getString("checkErrorType"));
this.supplierPartsInspection.setCheckErrorMsg(jsonObject.getString("checkErrorMsg"));
this.supplierPartsInspection.setMsgUuid(jsonObject.getString("msgUuid"));
//冗余字段
this.supplierPartsInspection.setCrtId(1);
this.supplierPartsInspection.setCrtDt(new Timestamp(System.currentTimeMillis()));
out.collect(this.supplierPartsInspection);
}
} catch (Exception e) {
log.error("flink-datasync>>>>>>>>>parse json value: {}", value);
log.error("flink-datasync>>>>>>>>>parse json error: {}" + e.getMessage());
}
}
}).uid("javabean-SupplierPartsInspection").name("Map JavaBean: SupplierPartsInspection");
//FLINK创建的视图会在内存中default_catalog.default_database
tableEnv.createTemporaryView("kafka_supplier_parts_inspection", (DataStream) javaBeanDs);
String catalogName = "flink-datasync_hive_catalog";
HiveCatalog catalog = new HiveCatalog(catalogName, Constants.TARGET_DB_NAME, Constants.HIVE_CONF);
tableEnv.registerCatalog(catalogName, (Catalog) catalog);
tableEnv.useCatalog(catalogName);
//设置为hive方言
tableEnv.getConfig().setSqlDialect(SqlDialect.HIVE);
tableEnv.executeSql("CREATE TABLE IF NOT EXISTS flink_k8s_kafka_hive ( line_inspect_status string ) PARTITIONED BY (dt string) STORED AS parquet TBLPROPERTIES ( 'sink.partition-commit.delay'='0 s', 'sink.partition-commit.trigger'='process-time', 'partition.time-extractor.timestamp-pattern'='$dt 00:00:00' , 'sink.partition-commit.policy.kind'='metastore,success-file' )");
tableEnv.executeSql("INSERT INTO TABLE flink_k8s_kafka_hive select hhPartNumber, from_unixtime(unix_timestamp(), 'yyyy-MM-dd') from default_catalog.default_database.kafka_supplier_parts_inspection");
}
public static KafkaSource<String> getKafkaSource(String[] topic, String groupId) {
return KafkaSource.builder()
.setBootstrapServers(Constants.KAFKA_SERVER)
.setTopics(topic)
.setGroupId(groupId)
//从kafka最早的偏移量开始消费
.setStartingOffsets(OffsetsInitializer.earliest())
.setValueOnlyDeserializer((DeserializationSchema) new SimpleStringSchema())
.build();
}
}
这个应用程序它使用 Apache Flink 从 Kafka 主题中读取数据,然后解析并转换它,最后将其写入 Hive 表。
2.resources目录:
flink-conf文件:
配置keytab文件和krb5文件本地路径
配置票据principal用户名
配置kubernetes客户端用户(这个要和票据所有者,票据principal,以及票据文件所有者保持一致,否则会出现GSS认证失败)
3.构建dockerfile
主要是将keytab,hive-site,flink-conf,krb5,lib,conf文件复制到镜像中(切记将文件keytab相关文件权限修改为777)
4.使用 HostAliases 向 Pod /etc/hosts 文件添加条目
这一步主要是让k8s集群解析到kdc机器地址(镜像中的hosts文件是不能直接修改的)
如果访问hive,也需要将全部Namenode地址配置上
5.krb5 配置
# cat /etc/krb5.conf
# Configuration snippets may be placed in this directory as well
includedir /etc/krb5.conf.d/
[logging]
default = FILE:/var/log/krb5libs.log
kdc = FILE:/var/log/krb5kdc.log
admin_server = FILE:/var/log/kadmind.log
[libdefaults]
dns_lookup_realm = false
ticket_lifetime = 24h
renew_lifetime = 7d
forwardable = true
rdns = false
default_realm = EXAMPLE.COM
default_ccache_name = KEYRING:persistent:%{uid}
[realms]
EXAMPLE.COM = {
kdc = kerberos.example.com
admin_server = kerberos.example.com
}
[domain_realm]
.example.com = EXAMPLE.COM
example.com = EXAMPLE.COM
[kdc]
profile = /var/kerberos/krb5kdc/kdc.conf
#以上是默认的文件信息
配置解释:
[logging] 这个组里面配置的是一些与日志存放路径相关的信息。
default: 用于配置krb5libs日志存放路径
kdc: 用于配置krb5kdc日志存放路径
admin_server: 用于配置kadmin服务日志存放路径
[libdefaults] 在使用Kerberos时使用的默认值。
dns_lookup_realm : 与dns参数有关,猜测用于域名解析,默认是false,当设置为true时,dns参数才起作用,有关dns参数获取其他参数可以查看官方文档。
ticket_lifetime: 获取到的票据存活时间,也就是该票据可以用多久,默认是1天
renew_lifetime:票据可更新的时间,也就是说该票据可以延迟到期的天数。默认是7天
forwardable:票据是否可以被kdc转发, 默认是true。
rdns :在进行dns解析的时候,正反向都可以,默认是true,如果dns_canonicalize_hostname参数被设置为false,那么rdns这个参数将无效。
default_realm:默认的领域,当客户端在连接或者获取主体的时候,当没有输入领域的时候,该值为默认值(列如:使用kinit admin/admin 获取主体的凭证时,没有输入领域,而传到kdc服务器的时候,会变成 admin/admin@EXAMPLE.COM ),这个值需要在[realms]中定义,如果有多个领域,都需要在realms中定义。默认以大写书写。
default_ccache_name:默认缓存的凭据名称,不推荐使用,当在客户端配置该参数的时候,会提示缓存错误信息。
[realms] 该组主要是配置领域相关的信息,默认只有一个,可以配置多个,如果配置多个(比如:HADOOP.COM, HBASE.COM),但是default_realm值定义为HADOOP.COM,那么客户端在获取与HBASE.COM相关的凭据时,需要指定具体的域名(比如客户端初始化凭据: kinit admin/admin@HBASE.COM).
EXAMPLE.COM: 简单说就是域名,对应用kerberos来说就是领域,相当于编程里面的namespace。
kdc : kdc服务器地址。格式 机器:端口, 默认端口是88
admin_server: admin服务地址 格式 机器:端口, 默认端口749
default_domain: 指定默认的域名
[domain_realm] 指定DNS域名和Kerberos域名之间映射关系。指定服务器的FQDN,对应的domain_realm值决定了主机所属的域。
[kdc]:
kdc的配置信息。即指定kdc.conf的位置。 profile :kdc的配置文件路径,默认值下若无文件则需要创建。
udp_preference_limit = 1 禁止使用udp可以防止一个Hadoop中的错误
udp_preference_limit = 1 kdc原生支持tcp/udp协议,客户端访问kdc服务时,默认先使用udp协议发起请求,如果数据包过大或者请求失败,然后再换用tcp协议请求。网络条件不好,如果使用udp容易出现丢包现象。
所以如果网络环境不好, udp_preference_limit设置为1,不然会出现认证失败的情况
6.查看kdc日志
可以通过kdc日志和程序运行日志,具体分析问题所在,比如发送到kdc的票据principal是否与预先设置是否一致,kdc服务器与客户端集群网络是否联通,客户端集群是否正确解析kdc\hdfs地址等
问题描述
1.ERROR: Unable to obtain password from user
2.ERROR: Identifier doesn't match expected value (906)
3.Caused by: javax.security.auth.login.LoginException: connect timed out
4.KrbException: Message stream modified (41)
解决方案:
1.保证本地和镜像中的keytab文件所有者和k8s system user 以及票据principal一致
1.在镜像路径/opt/kerberos/kerberos-keytab下存放keytab文件
1.在镜像路径/etc/下存放keytab文件
1.在镜像路径/opt/flink/conf/存放flink-conf文件
2.使用HostAliases 向 Pod /etc/hosts添加域名解析
2.将core-site.xml和hdfs-site.xml放入resources中并将hdfs-site和hive-site中的ip改为主机名
3.k8s集群与kdc所在集群网络不通
4.删除 krb5.conf 配置文件里的
renew_lifetime = xxx
这行配置即可