序幕
在Kubernetes中我最喜欢的就是发现服务的方式。 为什么?
主要是因为用户代码不必处理注册,查找服务,也没有网络意外(如果您曾经尝试过基于注册表的方法,那么您就会知道我在说什么) 。
这篇文章将介绍如何使用Fabric8以便使用CDI在Java中注入Kubernetes服务。
Kubernetes服务
深入介绍Kubernetes Services超出了本文的范围,但是我将尝试对其进行非常简要的概述。
在Kubernetes中,应用程序打包为Docker容器。 通常,将应用程序分成多个部分是一个好主意,因此您将拥有多个Docker容器,这些容器很可能需要彼此通信。 通过将某些容器放置在同一Pod中 ,可以将某些容器并置在一起,而另一些容器则可以位于较远的位置,并且需要彼此通信的方式。 这就是服务进入画面的地方。
容器可以绑定到一个或多个端口,从而为其他容器提供一个或多个“服务”。 例如:
- 数据库服务器。
- 消息代理。
- 休息服务。
问题是“ 其他容器如何知道如何访问这些服务? “
所以, Kubernetes让你“标签”每个吊舱 ,并使用这些标签来“选择” 吊舱提供一个逻辑服务。 这些标签是简单的键,值对。
这是一个示例,说明如何通过使用键名和值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"
} ]
}]
}
}
}
}
下面是我们如何定义一个例子服务暴露了MySQL端口。 服务选择器正在使用我们在上面指定的键/值对,以便定义提供服务的Pod。
{
"kind": "Service",
"apiVersion": "v1beta3",
"metadata": {
"name": "mysql"
},
"spec": {
"ports": [
{
"name": "mysql",
"protocol": "TCP",
"port": 3306,
"targetPort": 3306
}
],
"selector": {
"name": "mysql"
}
}
}
Kubernetes将服务信息作为环境变量传递给每个容器。 对于创建的每个容器, Kubernetes将确保为容器可见的所有服务传递适当的环境变量。
对于上述示例的mysql服务,环境变量将为:
- MYSQL_SERVICE_HOST
- MYSQL_SERVICE_PORT
Fabric8提供了一个CDI扩展,可以通过提供Kubernetes资源注入来简化Kubernetes应用程序的开发。
Fabric8 CDI扩展入门
要使用cdi扩展,第一步是将依赖项添加到项目中。
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>fabric8-cdi</artifactId>
<version>2.1.11</version>
</dependency>
下一步是确定要向哪个字段注入的服务,然后向其添加@ServiceName批注。
import javax.inject.Inject;
import io.fabric8.annotations.ServiceName;
public class MysqlExample {
private static final DB = "mydb";
private static final TCP_PROTO = "tcp";
private static final JDBC_PROTO = "jdbc:mysql";
private final Connection connection;
public MysqlExample(@Inject @ServiceName("mysql") String serivceUrl) {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection(toJdbcUrl(serivceUrl));
}
private static String toJdbcUrl(String url) {
return url.replaceFirst(TCP_PROTO, JDBC_PROTO) +
"/" +
DB;
}
//More stuff
}
在上面的示例中,我们有一个类,需要通过JDBC连接到可通过Kubernetes Services进行访问的mysql数据库。
注入的serivceUrl的格式为:[tcp | udp]:// [host]:[port]。 这是一个非常好的URL,但不是正确的jdbc url。 因此,我们需要一个实用程序来进行转换。 这是toJdbcUrl的目的。
即使可以在定义服务时指定协议,但只能指定核心传输协议,例如TCP或UDP,而不能指定http,jdbc等。
@Protocol批注
必须查找并用应用程序协议替换“ tcp”或“ udp”值,这很臭,而且很快就会变旧。 为了删除该样板, Fabric8提供了@Protocol批注。 该批注允许您在注入的服务URL中选择所需的应用程序协议。 在前面的示例中为“ jdbc:mysql”。 因此,代码可能类似于:
import javax.inject.Inject;
import io.fabric8.annotations.Protocol;
import io.fabric8.annotations.ServiceName;
public class MysqlExampleWithProtocol {
private static final DB = "mydb";
private final Connection connection;
public MysqlExampleWithProtocol(@Inject @Protocol("jdbc:mysql") @ServiceName("mysql") String serivceUrl) {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection(serivceUrl + "/" + DB);
}
//More stuff
}
毫无疑问,这要干净得多。 它仍然不包含有关实际数据库的信息或通常作为JDBC Url的一部分传递的任何参数,因此这里有改进的余地。
人们可能希望本着同样的精神,可以使用@Path或@Parameter批注,但这两者都是属于配置数据的,因此不适合将其硬编码为代码。 而且,Fabric8的CDI扩展并不希望成为URL转换框架。 因此,相反,它允许您直接实例化用于访问任何给定服务的客户端并将其注入源中,从而解决了问题。
使用@Factory注释为Services创建客户端
在前面的示例中,我们看到了如何获取服务的URL并使用该URL创建JDBC连接。 任何需要JDBC连接的项目都可以复制该代码段,并且只要用户记得他需要设置实际的数据库名称,它就可以很好地工作。
如果不是复制并粘贴该代码片段就可以对其进行组件化和重用,那岂不是很棒吗? 这是工厂注释的开始。您可以使用@Factory注释任何接受服务URL作为参数并返回使用URL创建的对象的方法(例如,服务的客户端)。 因此,对于前面的示例,我们可以有一个MysqlConnectionFactory:
import java.sql.Connection;
import io.fabric8.annotations.Factory;
import io.fabric8.annotations.ServiceName;
public class MysqlConnectionFactory {
@Factory
@ServiceName
public Connection createConnection(@ServiceName @Protocol("jdbc:mysql") String url) {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection(serivceUrl + "/" + DB);
}
}
然后,可以不注入URL而直接注入连接,如下所示。
import java.sql.Connection;
import javax.inject.Inject;
import io.fabric8.annotations.ServiceName;
public class MysqlExampleWithFactory {
private Connection connection;
public MysqlExampleWithProtocol(@Inject @ServiceName("mysql") Connection connection) {
this.connection = connection;
}
//More stuff
}
这里会发生什么?
当CDI应用程序启动时,Fabric8扩展将接收有关所有带注释方法的事件。 它将跟踪所有可用的工厂,因此对于使用@ServiceName注释的任何非String注入点,它将创建一个在后台使用匹配的@Factory的Producer。
在上面的示例中,首先将注册MysqlConnectionFactory,并且当检测到具有@ServiceName限定符的Connection实例时,将创建委托给MysqlConnectionFactory的Producer (将遵守所有限定符) 。
这很棒,但是也很简单 。 为什么?
因为很少有这样的工厂仅需要该服务的URL。 在大多数情况下,需要其他配置参数,例如:
- 认证信息
- 连接超时
- 更多 …。
将@Factory与@Configuration一起使用
在下一节中,我们将看到使用配置数据的工厂。 我将使用mysql jdbc示例,并添加对指定可配置凭据的支持。 但是在此之前,我要问一个反问的问题?
“如何配置容器化的应用程序?”
可能的最短答案是“使用环境变量”。
因此,在此示例中,我假设使用以下环境变量将凭据传递到需要访问mysql的容器:
- MYSQL_USERNAME
- MYSQL_PASSWORD
现在我们需要看看我们的@Factory如何使用它们。
我以前曾经想在CDI中使用环境变量,但是很有可能已经使用了Apache DeltaSpike 。 这个项目提供了@ConfigProperty批注,该批注允许您将环境变量注入CDI bean中(它的作用比实际更多) 。
import org.apache.deltaspike.core.api.config.ConfigProperty;
import javax.inject.Inject;
public class MysqlConfiguration {
@Inject
@ConfigProperty(name = "USERNAME", defaultValue = "admin")
private String username;
@Inject
@ConfigProperty(name = "PASSWORD", defaultValue = "admin")
private String password;
@Inject
@ConfigProperty(name = "DATABASE_NAME", defaultValue = "mydb")
private String databaseName;
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getDatabaseName() {
return databaseName;
}
}
该bean可以与@Factory方法结合使用,以便我们可以将配置传递给工厂本身。
但是,如果我们有多个数据库服务器,配置了不同的凭据集或多个数据库怎么办? 在这种情况下,我们可以使用服务名称作为前缀,并让Fabric8确定应该为每个@Configuration实例查找哪些环境变量。
import javax.inject.Inject;
import io.fabric8.annotations.ServiceName;
import io.fabric8.annotations.Factory;
import io.fabric8.annotations.Protocol;
import io.fabric8.annotations.Configuration;
public class MysqlExampleWithFactoryAndConfiguration {
@Factory
@ServiceName
public Connection createConnection(@ServiceName @Protocol("jdbc:mysql") String url, @Configuration MysqlConfiguration conf) {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection(serivceUrl + "/" + conf.getDatabaseName(),
conf.getUsername(),
conf.getPassword());
}
}
现在,我们有了一个可重用的组件,可以与在kubernetes内部运行的任何mysql数据库一起使用,并且可以完全配置。
Fabric8 CDI扩展中还有其他功能,但是由于本篇文章过长,以后的文章中将介绍它们。
敬请关注。