在 Kubernetes 中,我最喜欢的是发现服务的方式。为什么?
主要是因为用户代码不必处理注册、查找服务,也因为没有网络意外 (如果您曾经尝试过基于注册表的方法,您就会明白我在说什么) 。
这篇文章将介绍如何使用 Fabric8 以使用 CDI 在 Java 中注入 Kubernetes 服务。
Kubernetes 服务
深入介绍 Kubernetes 服务 超出了本文的范围,但我将尝试对它们进行非常简短的概述。
在 Kubernetes 中,应用程序被打包为 Docker 容器。通常,将应用程序拆分成单独的部分是个不错的主意,因此您将拥有多个很可能需要相互通信的 Docker 容器。一些容器可以通过将它们放在同一个 Pod 中并置在一起,而其他容器可能是远程的并且需要一种方式来相互交谈。这就是 服务 发挥作用的地方。
一个容器可以绑定到一个或多个端口,为其他容器提供一个或多个“服务”。例如:
- 数据库服务器。
- 消息代理。
- 休息服务。
问题是 “ 其他容器如何知道如何访问这些服务? ”
因此, Kubernetes 允许您“标记”每个 Pod ,并使用这些标签来“选择”提供逻辑服务的 Pod 。这些标签是简单的键值对。
这是一个示例,说明我们如何通过指定具有键 名 和值 mysql 的 标签来“标记”一个 pod。
{
"apiVersion" : "v1beta3",
"kind" : "ReplicationController",
"metadata" : {
"labels" : {
"name" : "mysql"
},
"name" : "mysql"
},
"spec" : {
"replicas" : 1,
"selector" : {
"name" : "mysql"
},
"template" : {
"metadata" : {
"labels" : {
"name" : "mysql"
}
},
"spec" : {
"containers" : [ {
"image" : "mysql",
"imagePullPolicy" : "IfNotPresent",
"name" : "mysql",
"ports" : [ {
"containerPort" : 3306,
"name" : "mysql"
} ]
}]
}
}
}
}
这是一个示例,说明我们如何定义一个公开 mysql 端口的 服务 。服务选择器使用我们上面指定的键/值对来定义提供服务的 pod。
{
"apiVersion" : "v1beta3",
"kind" : "ReplicationController",
"metadata" : {
"labels" : {
"name" : "mysql"
},
"name" : "mysql"
},
"spec" : {
"replicas" : 1,
"selector" : {
"name" : "mysql"
},
"template" : {
"metadata" : {
"labels" : {
"name" : "mysql"
}
},
"spec" : {
"containers" : [ {
"image" : "mysql",
"imagePullPolicy" : "IfNotPresent",
"name" : "mysql",
"ports" : [ {
"containerPort" : 3306,
"name" : "mysql"
} ]
}]
}
}
}
}
Kubernetes 将 服务 信息作为环境变量传递给每个容器。对于创建的每个容器, Kubernetes 将确保为容器可见的 所有 服务传递适当的环境变量。
对于上面示例的 mysql 服务,环境变量将为:
- MYSQL_SERVICE_HOST
- MYSQL_SERVICE_PORT
Fabric8 提供了一个 CDI 扩展,可用于通过提供 Kubernetes 资源注入来简化 Kubernetes 应用程序的开发。
Fabric8 CDI 扩展入门
要使用 cdi 扩展,第一步是将依赖项添加到项目中。
{
"apiVersion" : "v1beta3",
"kind" : "ReplicationController",
"metadata" : {
"labels" : {
"name" : "mysql"
},
"name" : "mysql"
},
"spec" : {
"replicas" : 1,
"selector" : {
"name" : "mysql"
},
"template" : {
"metadata" : {
"labels" : {
"name" : "mysql"
}
},
"spec" : {
"containers" : [ {
"image" : "mysql",
"imagePullPolicy" : "IfNotPresent",
"name" : "mysql",
"ports" : [ {
"containerPort" : 3306,
"name" : "mysql"
} ]
}]
}
}
}
}
下一步是决定要将哪个服务注入到哪个字段,然后向其添加 @ServiceName 注释。
{
"apiVersion" : "v1beta3",
"kind" : "ReplicationController",
"metadata" : {
"labels" : {
"name" : "mysql"
},
"name" : "mysql"
},
"spec" : {
"replicas" : 1,
"selector" : {
"name" : "mysql"
},
"template" : {
"metadata" : {
"labels" : {
"name" : "mysql"
}
},
"spec" : {
"containers" : [ {
"image" : "mysql",
"imagePullPolicy" : "IfNotPresent",
"name" : "mysql",
"ports" : [ {
"containerPort" : 3306,
"name" : "mysql"
} ]
}]
}
}
}
}
在上面的示例中,我们有一个类需要 JDBC 连接到 mysql 数据库,该数据库可通过 Kubernetes Services 获得。
注入的 serivceUrl 将采用以下形式:[tcp|udp]://[host]:[port]。这是一个非常好的 url,但它不是一个正确的 jdbc url。所以我们需要一个实用程序来转换它。这就是 toJdbcUrl 的目的。
尽管可以在定义服务时指定协议,但只能指定核心传输协议,例如 TCP 或 UDP,而不能指定 http、jdbc 等协议。
@Protocol 注解
必须用应用程序协议查找并替换“tcp”或“udp”值,这很臭,而且很快就会变旧。为了删除该样板, Fabric8 提供了 @Protocol 注释。此注释允许您在注入的服务 URL 中选择所需的应用程序协议。在前面的例子中就是“jdbc:mysql”。所以代码可能看起来像:
{
"apiVersion" : "v1beta3",
"kind" : "ReplicationController",
"metadata" : {
"labels" : {
"name" : "mysql"
},
"name" : "mysql"
},
"spec" : {
"replicas" : 1,
"selector" : {
"name" : "mysql"
},
"template" : {
"metadata" : {
"labels" : {
"name" : "mysql"
}
},
"spec" : {
"containers" : [ {
"image" : "mysql",
"imagePullPolicy" : "IfNotPresent",
"name" : "mysql",
"ports" : [ {
"containerPort" : 3306,
"name" : "mysql"
} ]
}]
}
}
}
}
毫无疑问,这样就干净多了。它仍然不包含有关实际数据库的信息或通常作为 JDBC Url 的一部分传递的任何参数,因此这里还有改进的余地。
人们会期望以同样的精神使用@Path 或@Parameter 注释,但这两者都是属于配置数据的东西,不适合硬编码到代码中。此外,Fabric8 的 CDI 扩展并不希望成为 URL 转换框架。因此,相反,它允许您直接实例化客户端以访问任何给定服务并将其注入源,从而使事情更上一层楼。
使用@Factory 注解为服务创建客户端
在前面的示例中,我们看到了如何获取服务的 url 并使用它创建 JDBC 连接。任何需要 JDBC 连接的项目都可以复制该片段并且它会很好地工作,只要用户记得他需要设置实际的数据库名称。
如果不是复制和粘贴该片段而是可以将其组件化并重用,那不是很好吗?这里是工厂注解的用武之地。您可以使用@Factory 对任何接受服务URL 作为参数并返回使用该URL 创建的对象(例如服务的客户端)的方法进行注解。所以对于前面的例子,我们可以有一个 MysqlConnectionFactory:
{
"apiVersion" : "v1beta3",
"kind" : "ReplicationController",
"metadata" : {
"labels" : {
"name" : "mysql"
},
"name" : "mysql"
},
"spec" : {
"replicas" : 1,
"selector" : {
"name" : "mysql"
},
"template" : {
"metadata" : {
"labels" : {
"name" : "mysql"
}
},
"spec" : {
"containers" : [ {
"image" : "mysql",
"imagePullPolicy" : "IfNotPresent",
"name" : "mysql",
"ports" : [ {
"containerPort" : 3306,
"name" : "mysql"
} ]
}]
}
}
}
}
然后可以直接注入连接而不是注入 URL,如下所示。
{
"apiVersion" : "v1beta3",
"kind" : "ReplicationController",
"metadata" : {
"labels" : {
"name" : "mysql"
},
"name" : "mysql"
},
"spec" : {
"replicas" : 1,
"selector" : {
"name" : "mysql"
},
"template" : {
"metadata" : {
"labels" : {
"name" : "mysql"
}
},
"spec" : {
"containers" : [ {
"image" : "mysql",
"imagePullPolicy" : "IfNotPresent",
"name" : "mysql",
"ports" : [ {
"containerPort" : 3306,
"name" : "mysql"
} ]
}]
}
}
}
}
这里发生了什么?
当 CDI 应用程序启动时,Fabric8 扩展将接收有关所有注释方法的事件。它将跟踪所有可用的工厂,因此对于使用@ServiceName 注释的任何非字符串注入点,它将创建一个在引擎盖下使用匹配的@Factory 的生产者。
在上面的示例中,首先 MysqlConnectionFactory 将被注册,当检测到带有 @ServiceName 限定符的 Connection 实例时,将创建一个委托给 MysqlConnectionFactory 的 Producer (将考虑所有限定符) 。
这很棒,但也很 简单 。为什么?
因为很少有这样的工厂只需要服务的 url。在大多数情况下,还需要其他配置参数,例如:
- 认证信息
- 连接超时
- 更多的 ....
将@Factory 与@Configuration 一起使用
在下一节中,我们将看到使用配置数据的工厂。我将使用 mysql jdbc 示例并添加对指定可配置凭据的支持。但在此之前我要问一个反问?
“怎么样,能配置一个容器化的应用吗?”
最短的答案是“使用环境变量”。
因此,在此示例中,我将假设使用以下环境变量将凭据传递给需要访问 mysql 的容器:
- MYSQL_用户名
- MYSQL_密码
现在我们需要看看我们的@Factory 如何使用它们。
我过去曾想在 CDI 中使用环境变量,很可能您使用过 Apache DeltaSpike 。除其他项目外,该项目还提供了 @ConfigProperty 注释,它允许您将环境变量注入 CDI bean (它实际上做的比这更多) 。
{
"apiVersion" : "v1beta3",
"kind" : "ReplicationController",
"metadata" : {
"labels" : {
"name" : "mysql"
},
"name" : "mysql"
},
"spec" : {
"replicas" : 1,
"selector" : {
"name" : "mysql"
},
"template" : {
"metadata" : {
"labels" : {
"name" : "mysql"
}
},
"spec" : {
"containers" : [ {
"image" : "mysql",
"imagePullPolicy" : "IfNotPresent",
"name" : "mysql",
"ports" : [ {
"containerPort" : 3306,
"name" : "mysql"
} ]
}]
}
}
}
}
这个 bean 可以与 @Factory 方法结合使用,这样我们就可以将配置传递给工厂本身。
但是,如果我们有 多个 数据库服务器,配置了一组不同的凭据或多个数据库怎么办?在这种情况下,我们可以使用服务名称作为前缀,让 Fabric8 确定它应该为每个 @Configuration 实例查找哪些环境变量。
{
"apiVersion" : "v1beta3",
"kind" : "ReplicationController",
"metadata" : {
"labels" : {
"name" : "mysql"
},
"name" : "mysql"
},
"spec" : {
"replicas" : 1,
"selector" : {
"name" : "mysql"
},
"template" : {
"metadata" : {
"labels" : {
"name" : "mysql"
}
},
"spec" : {
"containers" : [ {
"image" : "mysql",
"imagePullPolicy" : "IfNotPresent",
"name" : "mysql",
"ports" : [ {
"containerPort" : 3306,
"name" : "mysql"
} ]
}]
}
}
}
}
现在,我们有一个可重用的组件,可以与在 kubernetes 中运行的任何 mysql 数据库一起使用,并且是完全可配置的。
Fabric8 CDI 扩展中还有其他功能,但由于这篇文章已经太长,它们将在以后的文章中介绍。