本文为您介绍如何通过配置HAProxy反向代理,实现通过Gateway节点访问Presto服务。该方法也可以扩展到其他组件,例如Impala。

前提条件

  • 已创建普通集群或者高可用集群。

    创建集群详情,请参见创建集群

  • 已创建Gateway集群。

    创建Gateway集群详情,请参见创建Gateway

普通集群

普通集群配置Gateway时,只需要配置HAProxy反向代理,以便于对E-MapReduce(EMR)集群上Master节点的Presto Coodrinator的9090端口实现反向代理。

  1. 配置HAProxy。
    1. 通过SSH登录Gateway节点,详情请参见使用SSH连接主节点
    2. 执行以下命令,编辑HAProxy的配置文件haproxy.cfg
      vim /etc/haproxy/haproxy.cfg
    3. 添加以下内容。
      #---------------------------------------------------------------------
      # Global settings
      #---------------------------------------------------------------------
      global
      ......
      ## 配置代理,将Gateway的9090端口映射到emr-header-1.cluster-xxxx的9090端口。
      listen prestojdbc :9090
          mode tcp
          option tcplog
          balance source
          server presto-coodinator-1 emr-header-1.cluster-xxxx:9090
      修改完成后按Esc键,并输入:wq后按下回车键,保存并退出。
      说明 本文中的emr-header-1.cluster-xxxx,您可以通过hostname命令获取。
  2. 执行以下命令,重启HAProxy服务。
    service haproxy restart
  3. 配置以下安全组。
    方向 配置规则 说明
    公网入 自定义TCP,开放9090端口。 该端口用于HAProxy代理Master节点Coodinator端口。
访问Presto服务示例:

高安全集群

EMR高安全集群中的Presto服务使用Kerberos服务进行认证,其中Kerberos KDC服务位于emr-header-1上,端口为88,支持TCP/UDP协议。 使用Gateway访问高安全集群中的Presto服务,需要同时对Presto Coodinator服务端口和Kerberos KDC实现代理。EMR Presto Coodinator集群,默认使用Keystore配置的CN为emr-header-1,只能在内网使用,因此需要重新生成CN=emr-header-1.cluster-xxx的Keystore。
  • HTTPS认证相关
    1. 通过SSH方式登录集群,详情请参见使用SSH连接主节点
    2. 创建服务端CN=emr-header-1.cluster-xxx的Keystore。
      keytool -genkey -dname "CN=emr-header-1.cluster-xxx,OU=Alibaba,O=Alibaba,L=HZ, ST=zhejiang, C=CN" -alias server -keyalg RSA -keystore keystore -keypass 81ba14ce6084 -storepass 81ba14ce6084 -validity 36500
    3. 导出证书。
      keytool -export -alias server -file server.cer -keystore keystore -storepass 81ba14ce6084
    4. 制作客户端Keystore。
      keytool -genkey -dname "CN=myhost,OU=Alibaba,O=Alibaba,L=HZ, ST=zhejiang, C=CN" -alias client -keyalg RSA -keystore client.keystore -keypass 123456 -storepass 123456 -validity 36500
    5. 导入证书到客户端Keystore。
      keytool -import -alias server -keystore client.keystore -file server.cer -storepass 123456

      根据提示信息输入yes并回车,信任此证书。

    6. 拷贝生成的文件到客户端。
      scp root@xxx.xxx.xxx.xxx:/etc/ecm/presto-conf/client.keystore ./
      说明 本文中的xxx.xxx.xxx.xxx为主节点的公网IP地址。

      根据提示信息输入yes并回车。

  • Kerberos认证相关
    1. 通过SSH方式登录集群,详情请参见使用SSH连接主节点
    2. 添加Principal并导出keytab文件。
      1. 执行如下命令,进入Kerberos的admin工具。
        • EMR-3.30.0及后续版本和EMR-4.5.1及后续版本:
          sh /usr/lib/has-current/bin/admin-local.sh /etc/ecm/has-conf -k /etc/ecm/has-conf/admin.keytab
        • EMR-3.30.0之前版本和EMR-4.5.1之前版本:
          sh /usr/lib/has-current/bin/hadmin-local.sh /etc/ecm/has-conf -k /etc/ecm/has-conf/admin.keytab
      2. 执行如下命令,添加指定key的Principal。
        addprinc -pw 123456 clientuser
      3. 执行如下命令,导出keytab文件。
        ktadd -k /root/clientuser.keytab clientuser

        keytab文件默认导出至/root/目录下。

    3. 拷贝生成的文件到客户端。
      1. 拷贝文件clientuser.keytab到客户端。
        scp root@xxx.xxx.xxx.xxx:/root/clientuser.keytab ./

        根据提示信息输入主节点的密码。

      2. 拷贝文件krb5.conf到客户端。
        scp root@xxx.xxx.xxx.xxx:/etc/krb5.conf ./

        根据提示信息输入主节点的密码。

    4. 修改拷贝到客户端的krb5.conf文件,修改如下两处。
      1. 修改udp_preference_limit为1。因为HAProxy不支持UDP协议,所以修改使客户端使用TCP协议与KDC通信。
      2. 修改kdc 的值为Gateway的公网IP地址。
        [libdefaults]
            kdc_realm = EMR.***.COM
            default_realm = EMR.***.COM
            # 修改参数为1,使客户端使用TCP协议与KDC通信(因为HAProxy不支持UDP协议)。
            udp_preference_limit = 1 
            kdc_tcp_port = 88
            kdc_udp_port = 88
            dns_lookup_kdc = false
        [realms]
            EMR.xxx.COM = {
                # 设置为Gateway的外网IP。
                kdc = xxx.xxx.xxx.xxx:88
            }
    5. 修改客户端主机的hosts文件,添加以下内容。
      #  gateway ip
      xxx.xxx.xxx.xxx emr-header-1.cluster-xxx
  • 配置Gateway HAProxy。
    1. 通过SSH方式登录Gateway节点,详情请参见使用SSH连接主节点
    2. 编辑文件/etc/haproxy/haproxy.cfg,添加以下内容。
      #---------------------------------------------------------------------
      # Global settings
      #---------------------------------------------------------------------
      global
      ......
      listen prestojdbc :7778
          mode tcp
          option tcplog
          balance source
          server presto-coodinator-1 emr-header-1.cluster-xxx:7778
      listen kdc :88
          mode tcp
          option tcplog
          balance source
          server emr-kdc emr-header-1:88

      编辑完成后,保存退出。

    3. 执行以下命令,重启HAProxy服务。
      service haproxy restart
    4. 配置以下安全组规则。
      方向 配置规则 说明
      公网入 自定义UDP,开放88端口。 该端口用于HAProxy代理Master节点上的KDC。
      公网入 自定义TCP,开放88端口。 该端口用于HAProxy代理Master节点上的KDC。
      公网入 自定义TCP,开放7778端口。 该端口用于HAProxy代理Master节点的Coodinator端口。
  • 使用JDBC访问Presto,代码示例如下。
    try {
        Class.forName("com.facebook.presto.jdbc.PrestoDriver");
    } catch(ClassNotFoundException e) {
        LOG.error("Failed to load presto jdbc driver.", e);
        System.exit(-1);
    }
    Connection connection = null;
    Statement statement = null;
    try {
        String url = "jdbc:presto://emr-header-1.cluster-5****:7778/hive/default";
        Properties properties = new Properties();
        properties.setProperty("user", "hadoop");
        // https相关配置。
        properties.setProperty("SSL", "true");
        properties.setProperty("SSLTrustStorePath", "resources/5****/client.keystore");
        properties.setProperty("SSLTrustStorePassword", "123456");
        // Kerberos相关配置。
        properties.setProperty("KerberosRemoteServiceName", "presto");
        properties.setProperty("KerberosPrincipal", "clientuser@EMR.5****.COM");
        properties.setProperty("KerberosConfigPath", "resources/5****/krb5.conf");
        properties.setProperty("KerberosKeytabPath", "resources/5****/clientuser.keytab");
        // 创建连接对象。
        connection = DriverManager.getConnection(url, properties);
        // 创建Statement对象。
        statement = connection.createStatement();
        // 执行查询。
        ResultSet rs = statement.executeQuery("select * from table1");
        // 获取结果。
        int columnNum = rs.getMetaData().getColumnCount();
        int rowIndex = 0;
        while (rs.next()) {
            rowIndex++;
            for(int i = 1; i <= columnNum; i++) {
                System.out.println("Row " + rowIndex + ", Column " + i + ": " + rs.getString(i));
            }
        }
    } catch(SQLException e) {
        LOG.error("Exception thrown.", e);
    } finally {
        // 销毁Statement对象。
        if (statement != null) {
            try {
                statement.close();
                } catch(Throwable t) {
                  // No-ops
                }
        }
       // 关闭连接。
       if (connection != null) {
              try {
               connection.close();
           } catch(Throwable t) {
             // No-ops
           }
        }
    }