├── .doctmpl └── README.md ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── build.sbt ├── project ├── ReleaseProcess.scala ├── build.properties └── plugins.sbt ├── seednode-config-example └── src │ ├── k8s │ ├── akka-k8s-example-controller.yaml │ └── akka-k8s-example-service.yaml │ └── main │ ├── resources │ └── application.conf │ └── scala │ └── Main.scala ├── seednode-config └── src │ └── main │ └── scala │ └── de │ └── aktey │ └── akka │ └── k8s │ └── SeednodeConfig.scala └── version.sbt /.doctmpl/README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/ouven/akka-k8s-seednode.svg?branch=master)](https://travis-ci.org/ouven/akka-k8s-seednode) 2 | [![Maven Central](https://img.shields.io/maven-central/v/de.aktey.akka.k8s/seednode-config_2.11.svg?maxAge=2592000)](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22seednode-config_2.11%22) 3 | 4 | # Akka kubernetes seednode config 5 | The purpose of this project is to provide an easy way to run akka 6 | cluster applications on kubernetes. 7 | 8 | Current version: ${version} 9 | 10 | build.sbt 11 | ```sbt 12 | libraryDependencies += "de.aktey.akka.k8s" %% "seednode-config" % "${version}" 13 | ``` 14 | 15 | ## The problem 16 | You start your application on a dynamically assigned node. So you cannot know 17 | the seed nodes IP addresses, which needs to be configured at startup for akka 18 | cluster. 19 | 20 | ## Solution 21 | Kubernetes offers an API to look up the IP of a service. The endpoint of the API 22 | can be derived from environment variables, set by kubernetes. 23 | 24 | `de.aktey.akka.k8s.SeednodeConfig` asks the API for its service IPs, takes the 25 | first five elements and configures a `com.typesafe.config.Config` object for an 26 | akka cluster. The concrete name spaces, that are set are: 27 | - `akka.remote.netty.tcp.hostname` 28 | - `akka.cluster.seed-nodes` 29 | 30 | If no service IP is found (because no k8s service for a pod is started yet), 31 | `de.aktey.akka.k8s.SeednodeConfig#getConfig` will throw an exception, so the 32 | container can fail fast. The first container up, will see only one seed IP 33 | (its own) and will assume itself the cluster leader. 34 | 35 | ## Architectural 2 cents 36 | Akka doesn't seem to be designed for dynamic node assignment see 37 | [auto-downing section](http://doc.akka.io/docs/akka/snapshot/java/cluster-usage.html#Auto-downing__DO_NOT_USE_). 38 | So it seems to be a good advice to have a closer look at the warning box in that 39 | chapter. 40 | 41 | ## Design goals 42 | - As this is a base library, it should not introduce more any dependencies 43 | ([#nodependencies](https://index.scala-lang.org/search?q=nodependencies)). So 44 | this library uses only dependencies, that are already there by its nature: 45 | - scala 46 | - typesafe config (introduced by akka) 47 | - fail fast 48 | 49 | ## How to use 50 | ```scala 51 | import akka.actor._ 52 | import com.typesafe.config.ConfigFactory 53 | import de.aktey.akka.k8s.SeednodeConfig 54 | 55 | object Main extends App { 56 | 57 | val systemName = "akka-k8s" 58 | // create the seed node config 59 | val kubeConfig = K8sSeednodeConfig.getConfig(systemName) 60 | // load the rest of the config 61 | // resolve the variables introduced by `K8sSeednodeConfig.getConfig` 62 | val config = kubeConfig.withFallback(ConfigFactory.load()).resolve() 63 | 64 | // start the cluster node 65 | val system = ActorSystem(systemName, config) 66 | println(s"running : ${system.name}") 67 | } 68 | ``` 69 | 70 | Have a look at the example project. You can use it right away after building it with `sbt docker:publishLocal`. 71 | Then you can use the kubernetes definition files in `seednode-config-example/src/k8s` to run 72 | the example. 73 | 74 | ``` 75 | akka-k8s-seednode> kubectl create -f seednode-config-example/src/k8s 76 | replicationcontroller "akka-k8s-example" created 77 | service "akka-k8s-example" created 78 | 79 | akka-k8s-seednode> kubectl get svc 80 | NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE 81 | akka-k8s-example 10.0.0.121 8080/TCP,2552/TCP 8s 82 | kubernetes 10.0.0.1 443/TCP 5d 83 | 84 | akka-k8s-seednode> kubectl get rc 85 | NAME DESIRED CURRENT AGE 86 | akka-k8s-example 1 1 18s 87 | 88 | akka-k8s-seednode> kubectl get pod 89 | NAME READY STATUS RESTARTS AGE 90 | akka-k8s-example-ztehd 1/1 Running 0 21s 91 | 92 | akka-k8s-seednode> kubectl scale rc akka-k8s-example --replicas=3 93 | replicationcontroller "akka-k8s-example" scaled 94 | 95 | akka-k8s-seednode> kubectl get pod 96 | NAME READY STATUS RESTARTS AGE 97 | akka-k8s-example-ge388 1/1 Running 0 2s 98 | akka-k8s-example-q87dn 1/1 Running 0 2s 99 | akka-k8s-example-ztehd 1/1 Running 0 31s 100 | 101 | akka-k8s-seednode> kubectl logs akka-k8s-example-ztehd 102 | [...] 103 | 08:37:19.222 [akka-k8s-akka.actor.default-dispatcher-18] INFO a.cluster.Cluster(akka://akka-k8s) - Cluster Node [akka.tcp://akka-k8s@172.17.0.2:2552] - Node [akka.tcp://akka-k8s@172.17.0.2:2552] is JOINING, roles [] 104 | 08:37:19.233 [akka-k8s-akka.actor.default-dispatcher-4] INFO a.cluster.Cluster(akka://akka-k8s) - Cluster Node [akka.tcp://akka-k8s@172.17.0.2:2552] - Leader is moving node [akka.tcp://akka-k8s@172.17.0.2:2552] to [Up] 105 | 08:37:51.301 [akka-k8s-akka.actor.default-dispatcher-5] DEBUG akka.remote.Remoting - Associated [akka.tcp://akka-k8s@172.17.0.2:2552] <- [akka.tcp://akka-k8s@172.17.0.5:2552] 106 | 08:37:51.594 [akka-k8s-akka.actor.default-dispatcher-5] DEBUG akka.remote.Remoting - Associated [akka.tcp://akka-k8s@172.17.0.2:2552] <- [akka.tcp://akka-k8s@172.17.0.4:2552] 107 | 08:37:51.700 [akka-k8s-akka.actor.default-dispatcher-5] INFO a.cluster.Cluster(akka://akka-k8s) - Cluster Node [akka.tcp://akka-k8s@172.17.0.2:2552] - Node [akka.tcp://akka-k8s@172.17.0.5:2552] is JOINING, roles [] 108 | 08:37:51.859 [akka-k8s-akka.actor.default-dispatcher-3] INFO a.cluster.Cluster(akka://akka-k8s) - Cluster Node [akka.tcp://akka-k8s@172.17.0.2:2552] - Node [akka.tcp://akka-k8s@172.17.0.4:2552] is JOINING, roles [] 109 | 08:37:52.181 [akka-k8s-akka.actor.default-dispatcher-6] INFO a.cluster.Cluster(akka://akka-k8s) - Cluster Node [akka.tcp://akka-k8s@172.17.0.2:2552] - Leader is moving node [akka.tcp://akka-k8s@172.17.0.4:2552] to [Up] 110 | 08:37:52.189 [akka-k8s-akka.actor.default-dispatcher-6] INFO a.cluster.Cluster(akka://akka-k8s) - Cluster Node [akka.tcp://akka-k8s@172.17.0.2:2552] - Leader is moving node [akka.tcp://akka-k8s@172.17.0.5:2552] to [Up] 111 | [...] 112 | ``` -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | scala: 3 | - 2.11.8 4 | - 2.12.0 5 | jdk: 6 | - oraclejdk8 -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## 1.0.1 3 | * fix scala 2.12 deprecation warnings 4 | * add support for scala 2.12 5 | * upgrade library dependencies -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/ouven/akka-k8s-seednode.svg?branch=master)](https://travis-ci.org/ouven/akka-k8s-seednode) 2 | [![Maven Central](https://img.shields.io/maven-central/v/de.aktey.akka.k8s/seednode-config_2.11.svg?maxAge=2592000)](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22seednode-config_2.11%22) 3 | 4 | # Akka kubernetes seednode config 5 | The purpose of this project is to provide an easy way to run akka 6 | cluster applications on kubernetes. 7 | 8 | Current version: 1.0.1 9 | 10 | build.sbt 11 | ```sbt 12 | libraryDependencies += "de.aktey.akka.k8s" %% "seednode-config" % "1.0.1" 13 | ``` 14 | 15 | ## The problem 16 | You start your application on a dynamically assigned node. So you cannot know 17 | the seed nodes IP addresses, which needs to be configured at startup for akka 18 | cluster. 19 | 20 | ## Solution 21 | Kubernetes offers an API to look up the IP of a service. The endpoint of the API 22 | can be derived from environment variables, set by kubernetes. 23 | 24 | `de.aktey.akka.k8s.SeednodeConfig` asks the API for its service IPs, takes the 25 | first five elements and configures a `com.typesafe.config.Config` object for an 26 | akka cluster. The concrete name spaces, that are set are: 27 | - `akka.remote.netty.tcp.hostname` 28 | - `akka.cluster.seed-nodes` 29 | 30 | If no service IP is found (because no k8s service for a pod is started yet), 31 | `de.aktey.akka.k8s.SeednodeConfig#getConfig` will throw an exception, so the 32 | container can fail fast. The first container up, will see only one seed IP 33 | (its own) and will assume itself the cluster leader. 34 | 35 | ## Architectural 2 cents 36 | Akka doesn't seem to be designed for dynamic node assignment see 37 | [auto-downing section](http://doc.akka.io/docs/akka/snapshot/java/cluster-usage.html#Auto-downing__DO_NOT_USE_). 38 | So it seems to be a good advice to have a closer look at the warning box in that 39 | chapter. 40 | 41 | ## Design goals 42 | - As this is a base library, it should not introduce more any dependencies 43 | ([#nodependencies](https://index.scala-lang.org/search?q=nodependencies)). So 44 | this library uses only dependencies, that are already there by its nature: 45 | - scala 46 | - typesafe config (introduced by akka) 47 | - fail fast 48 | 49 | ## How to use 50 | ```scala 51 | import akka.actor._ 52 | import com.typesafe.config.ConfigFactory 53 | import de.aktey.akka.k8s.SeednodeConfig 54 | 55 | object Main extends App { 56 | 57 | val systemName = "akka-k8s" 58 | // create the seed node config 59 | val kubeConfig = K8sSeednodeConfig.getConfig(systemName) 60 | // load the rest of the config 61 | // resolve the variables introduced by `K8sSeednodeConfig.getConfig` 62 | val config = kubeConfig.withFallback(ConfigFactory.load()).resolve() 63 | 64 | // start the cluster node 65 | val system = ActorSystem(systemName, config) 66 | println(s"running : ") 67 | } 68 | ``` 69 | 70 | Have a look at the example project. You can use it right away after building it with `sbt docker:publishLocal`. 71 | Then you can use the kubernetes definition files in `seednode-config-example/src/k8s` to run 72 | the example. 73 | 74 | ``` 75 | akka-k8s-seednode> kubectl create -f seednode-config-example/src/k8s 76 | replicationcontroller "akka-k8s-example" created 77 | service "akka-k8s-example" created 78 | 79 | akka-k8s-seednode> kubectl get svc 80 | NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE 81 | akka-k8s-example 10.0.0.121 8080/TCP,2552/TCP 8s 82 | kubernetes 10.0.0.1 443/TCP 5d 83 | 84 | akka-k8s-seednode> kubectl get rc 85 | NAME DESIRED CURRENT AGE 86 | akka-k8s-example 1 1 18s 87 | 88 | akka-k8s-seednode> kubectl get pod 89 | NAME READY STATUS RESTARTS AGE 90 | akka-k8s-example-ztehd 1/1 Running 0 21s 91 | 92 | akka-k8s-seednode> kubectl scale rc akka-k8s-example --replicas=3 93 | replicationcontroller "akka-k8s-example" scaled 94 | 95 | akka-k8s-seednode> kubectl get pod 96 | NAME READY STATUS RESTARTS AGE 97 | akka-k8s-example-ge388 1/1 Running 0 2s 98 | akka-k8s-example-q87dn 1/1 Running 0 2s 99 | akka-k8s-example-ztehd 1/1 Running 0 31s 100 | 101 | akka-k8s-seednode> kubectl logs akka-k8s-example-ztehd 102 | [...] 103 | 08:37:19.222 [akka-k8s-akka.actor.default-dispatcher-18] INFO a.cluster.Cluster(akka://akka-k8s) - Cluster Node [akka.tcp://akka-k8s@172.17.0.2:2552] - Node [akka.tcp://akka-k8s@172.17.0.2:2552] is JOINING, roles [] 104 | 08:37:19.233 [akka-k8s-akka.actor.default-dispatcher-4] INFO a.cluster.Cluster(akka://akka-k8s) - Cluster Node [akka.tcp://akka-k8s@172.17.0.2:2552] - Leader is moving node [akka.tcp://akka-k8s@172.17.0.2:2552] to [Up] 105 | 08:37:51.301 [akka-k8s-akka.actor.default-dispatcher-5] DEBUG akka.remote.Remoting - Associated [akka.tcp://akka-k8s@172.17.0.2:2552] <- [akka.tcp://akka-k8s@172.17.0.5:2552] 106 | 08:37:51.594 [akka-k8s-akka.actor.default-dispatcher-5] DEBUG akka.remote.Remoting - Associated [akka.tcp://akka-k8s@172.17.0.2:2552] <- [akka.tcp://akka-k8s@172.17.0.4:2552] 107 | 08:37:51.700 [akka-k8s-akka.actor.default-dispatcher-5] INFO a.cluster.Cluster(akka://akka-k8s) - Cluster Node [akka.tcp://akka-k8s@172.17.0.2:2552] - Node [akka.tcp://akka-k8s@172.17.0.5:2552] is JOINING, roles [] 108 | 08:37:51.859 [akka-k8s-akka.actor.default-dispatcher-3] INFO a.cluster.Cluster(akka://akka-k8s) - Cluster Node [akka.tcp://akka-k8s@172.17.0.2:2552] - Node [akka.tcp://akka-k8s@172.17.0.4:2552] is JOINING, roles [] 109 | 08:37:52.181 [akka-k8s-akka.actor.default-dispatcher-6] INFO a.cluster.Cluster(akka://akka-k8s) - Cluster Node [akka.tcp://akka-k8s@172.17.0.2:2552] - Leader is moving node [akka.tcp://akka-k8s@172.17.0.4:2552] to [Up] 110 | 08:37:52.189 [akka-k8s-akka.actor.default-dispatcher-6] INFO a.cluster.Cluster(akka://akka-k8s) - Cluster Node [akka.tcp://akka-k8s@172.17.0.2:2552] - Leader is moving node [akka.tcp://akka-k8s@172.17.0.5:2552] to [Up] 111 | [...] 112 | ``` 113 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import com.typesafe.sbt.packager.docker.{Cmd, ExecCmd} 2 | 3 | val `common-settings` = Seq( 4 | scalacOptions ++= Seq("-deprecation", "-feature"), 5 | 6 | homepage := Some(url("https://github.com/ouven/akka-k8s-seednode/wiki")), 7 | licenses := Seq( 8 | "Apache License Version 2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0"), 9 | "The New BSD License" -> url("http://www.opensource.org/licenses/bsd-license.html") 10 | ), 11 | 12 | sources in EditSource ++= (baseDirectory.value / ".doctmpl" / "README.md").get, 13 | targetDirectory in EditSource := baseDirectory.value, 14 | variables in EditSource += "version" -> version.value, 15 | 16 | // relase with sbt-pgp plugin 17 | releasePublishArtifactsAction := PgpKeys.publishSigned.value, 18 | releaseProcess := ReleaseProcess.steps, 19 | 20 | publishTo := { 21 | val nexus = "https://oss.sonatype.org/" 22 | if (version.value.trim.endsWith("SNAPSHOT")) 23 | Some("snapshots" at nexus + "content/repositories/snapshots") 24 | else 25 | Some("releases" at nexus + "service/local/staging/deploy/maven2") 26 | }, 27 | publishMavenStyle := true, 28 | publishArtifact in Test := false, 29 | 30 | pomIncludeRepository := { _ => false }, 31 | pomExtra := 32 | github 33 | https://github.com/ouven/akka-k8s-seednode/issues 34 | 35 | 36 | 37 | Ruben Wagner 38 | https://github.com/ouven 39 | 40 | owner 41 | developer 42 | 43 | +1 44 | 45 | 46 | 47 | git@github.com:ouven/akka-k8s-seednode.git 48 | scm:git:git@github.com:ouven/akka-k8s-seednode.git 49 | scm:git:git@github.com:ouven/akka-k8s-seednode.git 50 | , 51 | 52 | organization := "de.aktey.akka.k8s", 53 | scalaVersion := "2.12.0", 54 | crossScalaVersions := Seq("2.11.8", "2.12.0") 55 | ) 56 | 57 | lazy val `akka-k8s-seednode` = project.in(file(".")) 58 | .aggregate(`seednode-config`, `seednode-config-example`) 59 | .settings(`common-settings`: _*) 60 | .settings( 61 | publishArtifact := false 62 | ) 63 | 64 | lazy val `seednode-config` = project 65 | .settings(`common-settings`: _*) 66 | .settings( 67 | libraryDependencies ++= Seq( 68 | "com.typesafe" % "config" % "1.3.1" 69 | ) 70 | ) 71 | 72 | val akkaVersion = "2.4.12" 73 | val logbackVersion = "1.1.3" 74 | 75 | lazy val `seednode-config-example` = project 76 | .enablePlugins(JavaAppPackaging) 77 | .dependsOn(`seednode-config`) 78 | .settings(`common-settings`: _*) 79 | .settings( 80 | publishArtifact := false, 81 | libraryDependencies ++= Seq( 82 | "com.typesafe.akka" %% "akka-actor" % akkaVersion, 83 | "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, 84 | "com.typesafe.akka" %% "akka-cluster" % akkaVersion, 85 | "ch.qos.logback" % "logback-classic" % logbackVersion 86 | ), 87 | // docker settings 88 | dockerBaseImage := "java:jre-alpine", 89 | dockerExposedPorts += 2551, 90 | dockerCommands := { 91 | val insertPoint = 2 92 | dockerCommands.value.take(insertPoint) ++ Seq( 93 | Cmd("USER", "root"), 94 | ExecCmd("RUN", "apk", "--update", "add", "bash") 95 | ) ++ dockerCommands.value.drop(insertPoint) 96 | }, 97 | version in Docker := "latest" 98 | ) 99 | -------------------------------------------------------------------------------- /project/ReleaseProcess.scala: -------------------------------------------------------------------------------- 1 | 2 | import org.clapper.sbt.editsource.EditSourcePlugin 3 | import org.clapper.sbt.editsource.EditSourcePlugin.autoImport._ 4 | import sbt.Keys._ 5 | import sbt.{IO, Project} 6 | import sbtrelease.ReleasePlugin.autoImport.ReleaseTransformations._ 7 | import sbtrelease.ReleasePlugin.autoImport._ 8 | 9 | object ReleaseProcess { 10 | lazy val generateDoc = ReleaseStep(action = st => { 11 | val proj = Project.extract(st) 12 | val vcs = proj.get(releaseVcs).getOrElse(sys.error("Aborting release. Working directory is not a repository of a recognized VCS.")) 13 | val ref = proj.get(thisProjectRef) 14 | val base = vcs.baseDir 15 | 16 | val (cst, _) = proj.runTask(EditSourcePlugin.autoImport.clean in EditSource in ref, st) 17 | val (nst, files) = proj.runTask(edit in EditSource in ref, cst) 18 | vcs.add(files.flatMap(IO.relativize(base, _)): _*) !! st.log 19 | nst 20 | }) 21 | 22 | lazy val steps = Seq[ReleaseStep]( 23 | checkSnapshotDependencies, 24 | inquireVersions, 25 | runTest, 26 | setReleaseVersion, 27 | generateDoc, 28 | commitReleaseVersion, 29 | tagRelease, 30 | publishArtifacts, 31 | setNextVersion, 32 | commitNextVersion, 33 | pushChanges 34 | ) 35 | } -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.13 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.1.4") 2 | 3 | addSbtPlugin("io.spray" % "sbt-revolver" % "0.8.0") 4 | 5 | addSbtPlugin("org.clapper" % "sbt-editsource" % "0.7.0") 6 | 7 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.3") 8 | 9 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") -------------------------------------------------------------------------------- /seednode-config-example/src/k8s/akka-k8s-example-controller.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ReplicationController 3 | metadata: 4 | name: akka-k8s-example 5 | spec: 6 | replicas: 1 7 | selector: 8 | app: akka-k8s-example 9 | template: 10 | metadata: 11 | labels: 12 | app: akka-k8s-example 13 | spec: 14 | containers: 15 | - env: 16 | - name: POD_NAMESPACE 17 | valueFrom: 18 | fieldRef: 19 | fieldPath: metadata.namespace 20 | - name: SERVICE_NAME 21 | value: akka-k8s-example 22 | - name: POD_IP 23 | valueFrom: 24 | fieldRef: 25 | fieldPath: status.podIP 26 | image: seednode-config-example:latest 27 | imagePullPolicy: Never 28 | name: akka-k8s-example 29 | ports: 30 | - containerPort: 8080 31 | name: http 32 | - containerPort: 2552 33 | name: seed 34 | -------------------------------------------------------------------------------- /seednode-config-example/src/k8s/akka-k8s-example-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: akka-k8s-example 6 | name: akka-k8s-example 7 | spec: 8 | ports: 9 | - name: http 10 | port: 8080 11 | - name: seed 12 | port: 2552 13 | type: NodePort 14 | selector: 15 | app: akka-k8s-example 16 | -------------------------------------------------------------------------------- /seednode-config-example/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | 3 | loggers = [akka.event.slf4j.Slf4jLogger] 4 | loglevel = DEBUG 5 | 6 | actor { 7 | provider = "akka.cluster.ClusterActorRefProvider" 8 | } 9 | 10 | remote { 11 | log-remote-lifecycle-events = on 12 | } 13 | 14 | cluster { 15 | auto-down-unreachable-after = 30s 16 | } 17 | } -------------------------------------------------------------------------------- /seednode-config-example/src/main/scala/Main.scala: -------------------------------------------------------------------------------- 1 | import akka.actor._ 2 | import com.typesafe.config.ConfigFactory 3 | import de.aktey.akka.k8s.SeednodeConfig 4 | 5 | object Main extends App { 6 | 7 | val systemName = "akka-k8s" 8 | // create the seed node config 9 | val kubeConfig = SeednodeConfig.getConfig(systemName) 10 | // load the rest of the config 11 | // resolve the variables introduced by `K8sSeednodeConfig.getConfig` 12 | val config = kubeConfig.withFallback(ConfigFactory.load()).resolve() 13 | 14 | // start the cluster node 15 | val system = ActorSystem(systemName, config) 16 | println(s"running : ${system.name}") 17 | } 18 | -------------------------------------------------------------------------------- /seednode-config/src/main/scala/de/aktey/akka/k8s/SeednodeConfig.scala: -------------------------------------------------------------------------------- 1 | package de.aktey.akka.k8s 2 | 3 | import java.net.URL 4 | import java.security.SecureRandom 5 | import java.security.cert.X509Certificate 6 | import javax.net.ssl._ 7 | import scala.collection.JavaConverters._ 8 | 9 | 10 | import com.typesafe.config.{Config, ConfigFactory, ConfigParseOptions, ConfigSyntax} 11 | 12 | import scala.io.Source 13 | 14 | /** inspired by io.k8s.cassandra.KubernetesSeedProvider 15 | * of the https://github.com/kubernetes/kubernetes project 16 | */ 17 | object SeednodeConfig { 18 | 19 | private val env: String ⇒ Option[String] = sys.env.get 20 | 21 | val host = env("KUBERNETES_PORT_443_TCP_ADDR").getOrElse("kubernetes.default.svc.cluster.local") 22 | val port = env("KUBERNETES_PORT_443_TCP_PORT").getOrElse("443") 23 | val podNamespace = env("POD_NAMESPACE").getOrElse("default") 24 | val podIp = env("POD_IP").getOrElse("0.0.0.0") 25 | val accountToken = env("K8S_ACCOUNT_TOKEN").getOrElse("/var/run/secrets/kubernetes.io/serviceaccount/token") 26 | 27 | val serviceName = env("SERVICE_NAME") 28 | .getOrElse(throw new IllegalArgumentException("environment variable SERVICE_NAME is not set")) 29 | 30 | // TODO: Load the CA cert when it is available on all platforms. 31 | private val trustAll = Array[TrustManager]( 32 | new X509TrustManager() { 33 | override def getAcceptedIssuers: Array[X509Certificate] = null 34 | 35 | override def checkClientTrusted(x509Certificates: Array[X509Certificate], s: String): Unit = () 36 | 37 | override def checkServerTrusted(x509Certificates: Array[X509Certificate], s: String): Unit = () 38 | } 39 | ) 40 | 41 | private val trustAllHosts = new HostnameVerifier { 42 | override def verify(hostname: String, session: SSLSession) = true 43 | } 44 | 45 | /** get all available service IPs as potential seeds */ 46 | def seedIps: List[String] = { 47 | val token = Source.fromFile(accountToken).mkString 48 | 49 | val sslCtx = SSLContext.getInstance("SSL") 50 | sslCtx.init(null, trustAll, new SecureRandom()) 51 | 52 | // TODO: Remove this once the CA cert is propagated everywhere, and replace with loading the CA cert. 53 | val url = s"https://$host:$port/api/v1/namespaces/$podNamespace/endpoints/$serviceName" 54 | val conn = new URL(url).openConnection().asInstanceOf[HttpsURLConnection] 55 | conn.setHostnameVerifier(trustAllHosts) 56 | conn.setSSLSocketFactory(sslCtx.getSocketFactory) 57 | conn.addRequestProperty("Authorization", "Bearer " + token) 58 | 59 | val json = Source.fromInputStream(conn.getInputStream).mkString 60 | 61 | // using config, so I don't need another framework 62 | for { 63 | subsets <- ConfigFactory 64 | .parseString(json, ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) 65 | .getConfigList("subsets").asScala.toList 66 | addresses <- subsets.getConfigList("addresses").asScala.toList 67 | } yield addresses.getString("ip") 68 | } 69 | 70 | /** return a config, that contains at most 5 seed node entries */ 71 | def getConfig(systemName: String): Config = { 72 | val ipConf = seedIps.take(5).map(ip => s""""akka.tcp://$systemName@$ip:"$${akka.remote.netty.tcp.port}""") 73 | val cfg = 74 | s""" 75 | |akka { 76 | | remote.netty.tcp.hostname = "$podIp" 77 | | cluster.seed-nodes= [ 78 | | ${ipConf.mkString(",")} 79 | | ] 80 | |}""".stripMargin 81 | 82 | ConfigFactory.parseString(cfg, ConfigParseOptions.defaults().setSyntax(ConfigSyntax.CONF)) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "1.0.2-SNAPSHOT" 2 | --------------------------------------------------------------------------------