diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ac9c8d2b..334f8259 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -18,8 +18,8 @@ jobs:
permissions:
contents: read
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-java@v3
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
with:
java-version: "17"
distribution: "adopt"
diff --git a/README-JP.md b/README-JP.md
index f62ffa7a..2ebcc728 100644
--- a/README-JP.md
+++ b/README-JP.md
@@ -16,6 +16,7 @@ Pluginは [Releases](https://github.com/AzisabaNetwork/Kuvel/releases/latest)
からダウンロードできます。 `Kuvel.jar` をダウンロードしVelocityに導入してください。ダウンロード後、コンフィグの設定を行ってください。
```yml
+namespace: ""
redis:
group-name: "develop" # Redisサーバーが同じかつgroup-nameが同じサーバー間でのみ名前同期が行われます
connection:
@@ -29,6 +30,14 @@ label-selectors:
- "kuvel.azisaba.net/enable-server-discovery=true"
```
+環境変数を指定してKuvelを設定することもできます。環境変数はconfig.ymlよりも優先され、以下の項目が設定可能です:
+- `KUVEL_NAMESPACE`
+- `KUVEL_REDIS_GROUPNAME`
+- `KUVEL_REDIS_CONNECTION_HOSTNAME`
+- `KUVEL_REDIS_CONNECTION_PORT`
+- `KUVEL_REDIS_CONNECTION_USERNAME`
+- `KUVEL_REDIS_CONNECTION_PASSWORD`
+
Kuvelがサーバーを監視するためには、Kubernetesに対して権限を要求しなければなりません。VelocityのPodに対してPodとReplicaSetのget/list/watchを許可してください
```yml
@@ -167,4 +176,4 @@ Kubernetesクラスター内ではPodがほぼ同時に作成されることが
## ライセンス
-[GNU General Public License v3.0](LICENSE)
\ No newline at end of file
+[GNU General Public License v3.0](LICENSE)
diff --git a/README.md b/README.md
index fd0678ad..2fb2230a 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,8 @@ from [Releases](https://github.com/AzisabaNetwork/Kuvel/releases/latest). Downlo
install it into Velocity plugins directory. The config file requires initial setup as seen below.
```yml
+# The kubernetes namespace to use for the server discovery.
+namespace: ""
# Server name synchronization by Redis is required in load-balanced environments using multiple Velocity instances.
redis:
group-name: "develop"
@@ -33,6 +35,10 @@ label-selectors:
- "kuvel.azisaba.net/enable-server-discovery=true"
```
+Alternatively you can use environment variables to configure Kuvel. The environment variable will override
+ the config.yml and are `KUVEL_NAMESPACE`, `KUVEL_REDIS_GROUPNAME`, `KUVEL_REDIS_CONNECTION_HOSTNAME`,
+`KUVEL_REDIS_CONNECTION_PORT`, `KUVEL_REDIS_CONNECTION_USERNAME`, and `KUVEL_REDIS_CONNECTION_PASSWORD`.
+
In order for Kuvel to monitor the server, you must request permission from Kubernetes. For Velocity pods, please allow get/list/watch to Pods
and ReplicaSets.
diff --git a/pom.xml b/pom.xml
index b4723bd0..c6201bc1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
net.azisaba
Kuvel
- 3.0.0-rc.2
+ 3.0.0-rc3
jar
${project.artifactId}
@@ -32,7 +32,7 @@
org.apache.maven.plugins
maven-compiler-plugin
- 3.10.1
+ 3.13.0
${java.version}
@@ -41,7 +41,7 @@
org.apache.maven.plugins
maven-shade-plugin
- 3.4.1
+ 3.6.0
package
@@ -74,28 +74,34 @@
com.velocitypowered
velocity-api
- 3.1.0
+ 3.1.1
provided
+
+ io.fabric8
+ kubernetes-client-api
+ 6.13.3
+
io.fabric8
kubernetes-client
- 6.3.1
+ 6.13.3
+ runtime
redis.clients
jedis
- 4.3.1
+ 5.1.4
org.apache.commons
commons-lang3
- 3.12.0
+ 3.16.0
org.projectlombok
lombok
- 1.18.26
+ 1.18.34
provided
diff --git a/src/main/java/net/azisaba/kuvel/Kuvel.java b/src/main/java/net/azisaba/kuvel/Kuvel.java
index 6296b22f..d5c8171c 100644
--- a/src/main/java/net/azisaba/kuvel/Kuvel.java
+++ b/src/main/java/net/azisaba/kuvel/Kuvel.java
@@ -5,13 +5,19 @@
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.plugin.Plugin;
+import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
+import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
-import io.fabric8.kubernetes.client.KubernetesClientBuilder;
-import java.util.Map.Entry;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
+
+import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import lombok.Getter;
import net.azisaba.kuvel.config.KuvelConfig;
import net.azisaba.kuvel.discovery.impl.redis.RedisLoadBalancerDiscovery;
@@ -25,7 +31,7 @@
@Plugin(
id = "kuvel",
name = "Kuvel",
- version = "3.0.0-rc",
+ version = "3.0.0-rc2",
url = "https://github.com/AzisabaNetwork/Kuvel",
description =
"Server-discovery Velocity plugin for Minecraft servers running in a Kubernetes cluster.",
@@ -35,6 +41,7 @@ public class Kuvel {
private final ProxyServer proxy;
private final Logger logger;
+ private final File dataDirectory;
private KubernetesClient client;
private KuvelServiceHandler kuvelServiceHandler;
@@ -45,9 +52,10 @@ public class Kuvel {
private KuvelConfig kuvelConfig;
@Inject
- public Kuvel(ProxyServer server, Logger logger) {
+ public Kuvel(ProxyServer server, org.slf4j.Logger logger, @DataDirectory Path dataDirectory) {
this.proxy = server;
this.logger = logger;
+ this.dataDirectory = dataDirectory.toFile();
}
@Subscribe
@@ -64,8 +72,7 @@ public void onProxyInitialization(ProxyInitializeEvent event) {
try {
kuvelConfig.load();
} catch (Exception e) {
- logger.severe("Failed to load config file. Plugin feature will be disabled.");
- e.printStackTrace();
+ logger.error("Failed to load config file. Plugin feature will be disabled.", e);
return;
}
@@ -75,11 +82,11 @@ public void onProxyInitialization(ProxyInitializeEvent event) {
}
getLogger().info("Loaded " + kuvelConfig.getLabelSelectors().size() + " selectors:");
- for (Entry entry : kuvelConfig.getLabelSelectors().entrySet()) {
+ for (Map.Entry entry : kuvelConfig.getLabelSelectors().entrySet()) {
getLogger().info(" - " + entry.getKey() + ": " + entry.getValue());
}
- kuvelServiceHandler = new KuvelServiceHandler(this, client);
+ kuvelServiceHandler = new KuvelServiceHandler(this, client, kuvelConfig.getNamespace());
Objects.requireNonNull(kuvelConfig.getRedisConnectionData());
Objects.requireNonNull(kuvelConfig.getProxyGroupName());
@@ -105,6 +112,7 @@ public void onProxyInitialization(ProxyInitializeEvent event) {
new RedisLoadBalancerDiscovery(
client,
this,
+ kuvelConfig.getNamespace(),
kuvelConfig.getRedisConnectionData().createJedisPool(),
kuvelConfig.getProxyGroupName(),
redisConnectionLeader,
@@ -114,6 +122,7 @@ public void onProxyInitialization(ProxyInitializeEvent event) {
new RedisServerDiscovery(
client,
this,
+ kuvelConfig.getNamespace(),
kuvelConfig.getRedisConnectionData().createJedisPool(),
kuvelConfig.getProxyGroupName(),
redisConnectionLeader,
diff --git a/src/main/java/net/azisaba/kuvel/KuvelServiceHandler.java b/src/main/java/net/azisaba/kuvel/KuvelServiceHandler.java
index e72dac80..5f3905a2 100644
--- a/src/main/java/net/azisaba/kuvel/KuvelServiceHandler.java
+++ b/src/main/java/net/azisaba/kuvel/KuvelServiceHandler.java
@@ -6,8 +6,6 @@
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodList;
import io.fabric8.kubernetes.client.KubernetesClient;
-import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
-import io.fabric8.kubernetes.client.dsl.PodResource;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
@@ -16,6 +14,8 @@
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
+
+import io.fabric8.kubernetes.client.dsl.PodResource;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.azisaba.kuvel.discovery.LoadBalancerDiscovery;
@@ -30,6 +30,7 @@ public class KuvelServiceHandler {
private final Kuvel plugin;
private final KubernetesClient client;
+ private final String namespace;
private final HashMap loadBalancerServerMap = new HashMap<>();
private final UidAndServerNameMap podUidAndServerNameMap = new UidAndServerNameMap();
@@ -128,7 +129,7 @@ public Optional getLoadBalancer(String serverName) {
private void updateLoadBalancerEndpoints(LoadBalancer loadBalancer) {
// TODO: This may be replaced by more improved function
FilterWatchListDeletable request = client.pods()
- .inAnyNamespace();
+ .inNamespace(namespace);
for (Entry e : plugin.getKuvelConfig().getLabelSelectors().entrySet()) {
request = request.withLabel(e.getKey(), e.getValue());
@@ -264,7 +265,7 @@ public boolean registerPod(Pod pod, String serverName) {
*/
public void registerPod(String podUid, String serverName) {
FilterWatchListDeletable request = client.pods()
- .inAnyNamespace();
+ .inNamespace(namespace);
for (Entry e : plugin.getKuvelConfig().getLabelSelectors().entrySet()) {
request = request.withLabel(e.getKey(), e.getValue());
diff --git a/src/main/java/net/azisaba/kuvel/config/KuvelConfig.java b/src/main/java/net/azisaba/kuvel/config/KuvelConfig.java
index 4aa2f90d..5bcf49a3 100644
--- a/src/main/java/net/azisaba/kuvel/config/KuvelConfig.java
+++ b/src/main/java/net/azisaba/kuvel/config/KuvelConfig.java
@@ -4,6 +4,7 @@
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
+import java.util.Map;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@@ -16,8 +17,9 @@ public class KuvelConfig {
private final Kuvel plugin;
- private static final String CONFIG_FILE_PATH = "./plugins/Kuvel/config.yml";
+ private static final String CONFIG_FILE_NAME = "config.yml";
+ @Nullable private String namespace;
private boolean redisEnabled;
@Nullable
private RedisConnectionData redisConnectionData;
@@ -26,25 +28,54 @@ public class KuvelConfig {
private final HashMap labelSelectors = new HashMap<>();
public void load() throws IOException {
- VelocityConfigLoader conf = VelocityConfigLoader.load(new File(CONFIG_FILE_PATH));
+ File uppercaseDataFolder = new File(plugin.getDataDirectory().getParentFile(), "Kuvel");
+ if (uppercaseDataFolder.exists() && !plugin.getDataDirectory().exists()) {
+ if (uppercaseDataFolder.renameTo(plugin.getDataDirectory())) {
+ plugin
+ .getLogger()
+ .info(
+ "Successfully renamed the data folder to use a lowercase name.");
+ } else {
+ plugin
+ .getLogger()
+ .warn(
+ "Failed to rename the data folder to be lowercase. Please manually rename the data folder to 'kuvel'.");
+ }
+ }
+
+ VelocityConfigLoader conf = VelocityConfigLoader.load(new File(plugin.getDataDirectory(), CONFIG_FILE_NAME));
conf.saveDefaultConfig();
- String hostname = conf.getString("redis.connection.hostname");
+ Map env = System.getenv();
+
+ namespace = env.getOrDefault("KUVEL_NAMESPACE", conf.getString("namespace", null));
+
+ String hostname = env.getOrDefault("KUVEL_REDIS_CONNECTION_HOSTNAME", conf.getString("redis.connection.hostname"));
int port = conf.getInt("redis.connection.port", -1);
- String username = conf.getString("redis.connection.username");
- String password = conf.getString("redis.connection.password");
+ if (env.containsKey("KUVEL_REDIS_CONNECTION_PORT")) {
+ try {
+ port = Integer.parseInt(env.get("KUVEL_REDIS_CONNECTION_PORT"));
+ } catch (NumberFormatException e) {
+ plugin
+ .getLogger()
+ .warn(
+ "Invalid port number for Redis connection specified in KUVEL_REDIS_CONNECTION_PORT environment variable. Using port " + port + " from config.yml.");
+ }
+ }
+ String username = env.getOrDefault("KUVEL_REDIS_CONNECTION_USERNAME", conf.getString("redis.connection.username"));
+ String password = env.getOrDefault("KUVEL_REDIS_CONNECTION_PASSWORD", conf.getString("redis.connection.password"));
if (hostname == null || port <= 0) {
redisEnabled = false;
plugin
.getLogger()
- .warning(
+ .warn(
"Redis is enabled, but hostname or port is invalid. Redis sync will be disabled.");
} else {
redisConnectionData = new RedisConnectionData(hostname, port, username, password);
}
- proxyGroupName = conf.getString("redis.group-name", null);
+ proxyGroupName = env.getOrDefault("KUVEL_REDIS_GROUPNAME", conf.getString("redis.group-name", null));
if (conf.isSet("label-selectors")) {
conf.getStringList("label-selectors").forEach(s -> {
diff --git a/src/main/java/net/azisaba/kuvel/discovery/impl/redis/RedisLoadBalancerDiscovery.java b/src/main/java/net/azisaba/kuvel/discovery/impl/redis/RedisLoadBalancerDiscovery.java
index 926738f1..e63348ba 100644
--- a/src/main/java/net/azisaba/kuvel/discovery/impl/redis/RedisLoadBalancerDiscovery.java
+++ b/src/main/java/net/azisaba/kuvel/discovery/impl/redis/RedisLoadBalancerDiscovery.java
@@ -6,18 +6,18 @@
import io.fabric8.kubernetes.api.model.apps.ReplicaSet;
import io.fabric8.kubernetes.api.model.apps.ReplicaSetList;
import io.fabric8.kubernetes.client.KubernetesClient;
-import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
-import io.fabric8.kubernetes.client.dsl.RollableScalableResource;
import java.net.InetSocketAddress;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
+
+import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
+import io.fabric8.kubernetes.client.dsl.RollableScalableResource;
import lombok.RequiredArgsConstructor;
import net.azisaba.kuvel.Kuvel;
import net.azisaba.kuvel.KuvelServiceHandler;
@@ -36,6 +36,7 @@ public class RedisLoadBalancerDiscovery implements LoadBalancerDiscovery {
private final KubernetesClient client;
private final Kuvel plugin;
+ private final String namespace;
private final JedisPool jedisPool;
private final String groupName;
private final RedisConnectionLeader redisConnectionLeader;
@@ -56,9 +57,9 @@ public void start() {
Runnable runnable =
() -> {
FilterWatchListDeletable> request = client.apps()
- .replicaSets().inAnyNamespace();
+ .replicaSets().inNamespace(namespace);
- for (Entry e : plugin.getKuvelConfig().getLabelSelectors().entrySet()) {
+ for (Map.Entry e : plugin.getKuvelConfig().getLabelSelectors().entrySet()) {
request = request.withLabel(e.getKey(), e.getValue());
}
@@ -242,9 +243,9 @@ public void registerLoadBalancersForStartup() {
}
FilterWatchListDeletable> request = client.apps()
- .replicaSets().inAnyNamespace();
+ .replicaSets().inNamespace(namespace);
- for (Entry e : plugin.getKuvelConfig().getLabelSelectors().entrySet()) {
+ for (Map.Entry e : plugin.getKuvelConfig().getLabelSelectors().entrySet()) {
request = request.withLabel(e.getKey(), e.getValue());
}
@@ -273,9 +274,9 @@ public void registerLoadBalancersForStartup() {
private ReplicaSet getReplicaSetFromUid(String uid) {
FilterWatchListDeletable> request = client.apps()
- .replicaSets().inAnyNamespace();
+ .replicaSets().inNamespace(namespace);
- for (Entry e : plugin.getKuvelConfig().getLabelSelectors().entrySet()) {
+ for (Map.Entry e : plugin.getKuvelConfig().getLabelSelectors().entrySet()) {
request = request.withLabel(e.getKey(), e.getValue());
}
diff --git a/src/main/java/net/azisaba/kuvel/discovery/impl/redis/RedisServerDiscovery.java b/src/main/java/net/azisaba/kuvel/discovery/impl/redis/RedisServerDiscovery.java
index 8449f824..23fecee7 100644
--- a/src/main/java/net/azisaba/kuvel/discovery/impl/redis/RedisServerDiscovery.java
+++ b/src/main/java/net/azisaba/kuvel/discovery/impl/redis/RedisServerDiscovery.java
@@ -4,8 +4,6 @@
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodList;
import io.fabric8.kubernetes.client.KubernetesClient;
-import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
-import io.fabric8.kubernetes.client.dsl.PodResource;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
@@ -13,10 +11,14 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
+
+import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
+import io.fabric8.kubernetes.client.dsl.PodResource;
import lombok.RequiredArgsConstructor;
import net.azisaba.kuvel.Kuvel;
import net.azisaba.kuvel.KuvelServiceHandler;
@@ -34,6 +36,7 @@ public class RedisServerDiscovery implements ServerDiscovery {
private final KubernetesClient client;
private final Kuvel plugin;
+ private final String namespace;
private final JedisPool jedisPool;
private final String groupName;
private final RedisConnectionLeader redisConnectionLeader;
@@ -52,7 +55,7 @@ public void start() {
Runnable runnable =
() -> {
FilterWatchListDeletable request = client.pods()
- .inAnyNamespace();
+ .inNamespace(namespace);
for (Entry e : plugin.getKuvelConfig().getLabelSelectors().entrySet()) {
request = request.withLabel(e.getKey(), e.getValue());
@@ -115,7 +118,7 @@ public HashMap getServersForStartup() {
jedis.hgetAll(RedisKeys.LOAD_BALANCERS_PREFIX.getKey() + groupName);
FilterWatchListDeletable request = client.pods()
- .inAnyNamespace();
+ .inNamespace(namespace);
for (Entry e : plugin.getKuvelConfig().getLabelSelectors().entrySet()) {
request = request.withLabel(e.getKey(), e.getValue());
@@ -161,16 +164,16 @@ public HashMap getServersForStartup() {
verb = "Found";
}
- for (String podUid : podIdToServerNameMap.keySet()) {
- plugin
- .getLogger()
- .info(verb + " server: " + podIdToServerNameMap.get(podUid) + " (" + podUid + ")");
- }
-
HashMap servers = new HashMap<>();
for (Entry entry : podIdToServerNameMap.entrySet()) {
+ plugin
+ .getLogger()
+ .info(verb + " server: " + entry.getValue() + " (" + entry.getKey() + ")");
Pod pod = getPodByUid(entry.getKey());
if (pod == null) {
+ plugin
+ .getLogger()
+ .warn("Pod " + entry.getKey() + " for server " + entry.getValue() + " not found");
continue;
}
diff --git a/src/main/java/net/azisaba/kuvel/redis/RedisConnectionLeader.java b/src/main/java/net/azisaba/kuvel/redis/RedisConnectionLeader.java
index 0444a6f4..187784d2 100644
--- a/src/main/java/net/azisaba/kuvel/redis/RedisConnectionLeader.java
+++ b/src/main/java/net/azisaba/kuvel/redis/RedisConnectionLeader.java
@@ -120,6 +120,7 @@ private void runDiscoveryTask() {
new RedisLoadBalancerDiscovery(
plugin.getClient(),
plugin,
+ plugin.getKuvelConfig().getNamespace(),
plugin.getKuvelConfig().getRedisConnectionData().createJedisPool(),
plugin.getKuvelConfig().getProxyGroupName(),
this,
@@ -131,6 +132,7 @@ private void runDiscoveryTask() {
new RedisServerDiscovery(
plugin.getClient(),
plugin,
+ plugin.getKuvelConfig().getNamespace(),
plugin.getKuvelConfig().getRedisConnectionData().createJedisPool(),
plugin.getKuvelConfig().getProxyGroupName(),
this,
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index a1e06117..eef9b19d 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -1,3 +1,5 @@
+# The kubernetes namespace to use for the server discovery.
+namespace: ""
# Server name synchronization by Redis is required in load-balanced environments using multiple Velocity.
redis:
group-name: "develop"