You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by ch...@apache.org on 2019/10/07 12:34:52 UTC

[openwhisk] branch master updated: Standalone OpenWhisk KubernetesContainerFactory support (#4671)

This is an automated email from the ASF dual-hosted git repository.

chetanm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openwhisk.git


The following commit(s) were added to refs/heads/master by this push:
     new 75d8507  Standalone OpenWhisk KubernetesContainerFactory support (#4671)
75d8507 is described below

commit 75d8507cc4264da3f57efe7433a45278ecbc578e
Author: Chetan Mehrotra <ch...@apache.org>
AuthorDate: Mon Oct 7 18:04:35 2019 +0530

    Standalone OpenWhisk KubernetesContainerFactory support (#4671)
    
    * Add support for port forwarding to remote pod
    * Add support to use KCF in Standalone mode
---
 core/invoker/src/main/resources/application.conf   |  4 ++
 .../kubernetes/KubernetesClient.scala              | 14 +++-
 .../kubernetes/KubernetesContainer.scala           | 11 +--
 core/standalone/README.md                          | 84 +++++++++++++++++++++-
 .../src/main/resources/standalone-kcf.conf         | 36 ++++++++++
 .../openwhisk/standalone/StandaloneOpenWhisk.scala | 12 +++-
 6 files changed, 150 insertions(+), 11 deletions(-)

diff --git a/core/invoker/src/main/resources/application.conf b/core/invoker/src/main/resources/application.conf
index ce0bcef..9afba4d 100644
--- a/core/invoker/src/main/resources/application.conf
+++ b/core/invoker/src/main/resources/application.conf
@@ -77,6 +77,10 @@ whisk {
       key: "openwhisk-role"
       value: "invoker"
     }
+
+    # Enables forwarding to remote port via a local random port. This mode is mostly useful
+    # for development via Standalone mode
+    port-forwarding-enabled = false
   }
 
   # Timeouts for runc commands. Set to "Inf" to disable timeout.
diff --git a/core/invoker/src/main/scala/org/apache/openwhisk/core/containerpool/kubernetes/KubernetesClient.scala b/core/invoker/src/main/scala/org/apache/openwhisk/core/containerpool/kubernetes/KubernetesClient.scala
index 777f069..31c7678 100644
--- a/core/invoker/src/main/scala/org/apache/openwhisk/core/containerpool/kubernetes/KubernetesClient.scala
+++ b/core/invoker/src/main/scala/org/apache/openwhisk/core/containerpool/kubernetes/KubernetesClient.scala
@@ -74,7 +74,8 @@ case class KubernetesInvokerNodeAffinity(enabled: Boolean, key: String, value: S
  */
 case class KubernetesClientConfig(timeouts: KubernetesClientTimeoutConfig,
                                   invokerAgent: KubernetesInvokerAgentConfig,
-                                  userPodNodeAffinity: KubernetesInvokerNodeAffinity)
+                                  userPodNodeAffinity: KubernetesInvokerNodeAffinity,
+                                  portForwardingEnabled: Boolean)
 
 /**
  * Serves as an interface to the Kubernetes API by proxying its REST API and/or invoking the kubectl CLI.
@@ -219,13 +220,20 @@ class KubernetesClient(
 
   protected def toContainer(pod: Pod): KubernetesContainer = {
     val id = ContainerId(pod.getMetadata.getName)
-    val addr = ContainerAddress(pod.getStatus.getPodIP)
+
+    val portFwd = if (config.portForwardingEnabled) {
+      Some(kubeRestClient.pods().withName(pod.getMetadata.getName).portForward(8080))
+    } else None
+
+    val addr = portFwd
+      .map(fwd => ContainerAddress("localhost", fwd.getLocalPort))
+      .getOrElse(ContainerAddress(pod.getStatus.getPodIP))
     val workerIP = pod.getStatus.getHostIP
     // Extract the native (docker or containerd) containerId for the container
     // By convention, kubernetes adds a docker:// prefix when using docker as the low-level container engine
     val nativeContainerId = pod.getStatus.getContainerStatuses.get(0).getContainerID.stripPrefix("docker://")
     implicit val kubernetes = this
-    new KubernetesContainer(id, addr, workerIP, nativeContainerId)
+    new KubernetesContainer(id, addr, workerIP, nativeContainerId, portFwd)
   }
 }
 
diff --git a/core/invoker/src/main/scala/org/apache/openwhisk/core/containerpool/kubernetes/KubernetesContainer.scala b/core/invoker/src/main/scala/org/apache/openwhisk/core/containerpool/kubernetes/KubernetesContainer.scala
index d37139b..be73bd4 100644
--- a/core/invoker/src/main/scala/org/apache/openwhisk/core/containerpool/kubernetes/KubernetesContainer.scala
+++ b/core/invoker/src/main/scala/org/apache/openwhisk/core/containerpool/kubernetes/KubernetesContainer.scala
@@ -25,6 +25,7 @@ import akka.stream.StreamLimitReachedException
 import akka.stream.scaladsl.Framing.FramingException
 import akka.stream.scaladsl.Source
 import akka.util.ByteString
+import io.fabric8.kubernetes.client.PortForward
 
 import scala.concurrent.ExecutionContext
 import scala.concurrent.Future
@@ -100,10 +101,11 @@ object KubernetesContainer {
 class KubernetesContainer(protected[core] val id: ContainerId,
                           protected[core] val addr: ContainerAddress,
                           protected[core] val workerIP: String,
-                          protected[core] val nativeContainerId: String)(implicit kubernetes: KubernetesApi,
-                                                                         override protected val as: ActorSystem,
-                                                                         protected val ec: ExecutionContext,
-                                                                         protected val logging: Logging)
+                          protected[core] val nativeContainerId: String,
+                          portForward: Option[PortForward] = None)(implicit kubernetes: KubernetesApi,
+                                                                   override protected val as: ActorSystem,
+                                                                   protected val ec: ExecutionContext,
+                                                                   protected val logging: Logging)
     extends Container {
 
   /** The last read timestamp in the log file */
@@ -120,6 +122,7 @@ class KubernetesContainer(protected[core] val id: ContainerId,
 
   override def destroy()(implicit transid: TransactionId): Future[Unit] = {
     super.destroy()
+    portForward.foreach(_.close())
     kubernetes.rm(this)
   }
 
diff --git a/core/standalone/README.md b/core/standalone/README.md
index d74a554..0dbdf09 100644
--- a/core/standalone/README.md
+++ b/core/standalone/README.md
@@ -75,7 +75,7 @@ $ java -jar openwhisk-standalone.jar -h
  \   \  /  \/    \___/| .__/ \___|_| |_|__/\__|_| |_|_|___/_|\_\
   \___\/ tm           |_|
 
-  -m, --manifest  <arg>            Manifest JSON defining the supported runtimes
+  -m, --manifest  <arg>            Manifest json defining the supported runtimes
   -c, --config-file  <arg>         application.conf which overrides the default
                                    standalone.conf
       --api-gw                     Enable API Gateway support
@@ -91,6 +91,8 @@ $ java -jar openwhisk-standalone.jar -h
       --api-gw-port  <arg>         API Gateway Port
       --clean                      Clean any existing state like database
   -d, --data-dir  <arg>            Directory used for storage
+      --dev-kcf                    Enables KubernetesContainerFactory for local
+                                   development
       --dev-mode                   Developer mode speeds up the startup by
                                    disabling preflight checks and avoiding
                                    explicit pulls.
@@ -271,6 +273,84 @@ Launched service details
 [ 3000  ] http://localhost:3000 (whisk-grafana)
 ```
 
+#### Using KubernetesContainerFactory
+
+Standalone OpenWhisk can be configured to use KubernetesContainerFactory (KCF) via `--dev-kcf` option. This mode can be used to
+simplify developing KubernetesContainerFactory.
+
+Below mentioned steps are based on [Kind][9] tool for running local Kubernetes clusters using Docker container "nodes".
+However this mode should work against any Kubernetes cluster if the the `KUBECONFIG` is properly set.
+
+##### 1. Install and configure Kind
+
+We would use Kind to setup a local k8s. Follow the steps [here][10] to create a simple cluster.
+
+```bash
+$ kind create cluster --wait 5m
+
+# Export the kind config for kubectl usage
+$ export KUBECONFIG="$(kind get kubeconfig-path)"
+
+# Configure the default namespace
+$ kubectl config set-context --current --namespace=default
+
+# See the config path
+$ kind get kubeconfig-path
+/Users/example/.kube/kind-config-kind
+```
+
+##### 2. Launch Standalone
+
+```bash
+# Launch it with `kubeconfig` system property set to kind config
+$ java  -Dkubeconfig="$(kind get kubeconfig-path)" -jar bin/openwhisk-standalone.jar --dev-kcf
+```
+
+Once started and required `.wskprops` configured to use the standalone server create a `hello.js` function
+
+```js
+function main(params) {
+    greeting = 'hello, world'
+    var hello = {payload: greeting}
+    var result = {...hello, ...process.env}
+    console.log(greeting);
+    return result
+}
+```
+
+```bash
+$ wsk action create hello hello.js
+$ wsk action invoke hello -br
+```
+
+This shows an output like below indicating that KubernetesContainerFactory based invocation is working properly.
+
+```
+{
+    "HOME": "/root",
+    "HOSTNAME": "wsk0-2-prewarm-nodejs10",
+    "KUBERNETES_PORT": "tcp://10.96.0.1:443",
+    "KUBERNETES_PORT_443_TCP": "tcp://10.96.0.1:443",
+    "KUBERNETES_PORT_443_TCP_ADDR": "10.96.0.1",
+    "KUBERNETES_PORT_443_TCP_PORT": "443",
+    "KUBERNETES_PORT_443_TCP_PROTO": "tcp",
+    "KUBERNETES_SERVICE_HOST": "10.96.0.1",
+    "KUBERNETES_SERVICE_PORT": "443",
+    "KUBERNETES_SERVICE_PORT_HTTPS": "443",
+    "NODE_VERSION": "10.15.3",
+    "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+    "PWD": "/nodejsAction",
+    "YARN_VERSION": "1.13.0",
+    "__OW_ACTION_NAME": "/guest/hello",
+    "__OW_ACTIVATION_ID": "71e48d2d62e142eca48d2d62e192ec2d",
+    "__OW_API_HOST": "http://host.docker.internal:3233",
+    "__OW_DEADLINE": "1570223213407",
+    "__OW_NAMESPACE": "guest",
+    "__OW_TRANSACTION_ID": "iSOoNklk6V7l7eh8KJnvugidKEmaNJmv",
+    "payload": "hello, world"
+}
+```
+
 [1]: https://github.com/apache/incubator-openwhisk/blob/master/docs/cli.md
 [2]: https://github.com/apache/incubator-openwhisk/blob/master/docs/samples.md
 [3]: https://github.com/apache/incubator-openwhisk-apigateway
@@ -279,3 +359,5 @@ Launched service details
 [6]: https://github.com/obsidiandynamics/kafdrop
 [7]: https://github.com/apache/openwhisk/blob/master/docs/metrics.md#user-specific-metrics
 [8]: https://github.com/apache/openwhisk/blob/master/core/monitoring/user-events/README.md
+[9]: https://kind.sigs.k8s.io/
+[10]: https://kind.sigs.k8s.io/docs/user/quick-start/
diff --git a/core/standalone/src/main/resources/standalone-kcf.conf b/core/standalone/src/main/resources/standalone-kcf.conf
new file mode 100644
index 0000000..0608be0
--- /dev/null
+++ b/core/standalone/src/main/resources/standalone-kcf.conf
@@ -0,0 +1,36 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+include classpath("standalone.conf")
+
+whisk {
+  spi {
+    ContainerFactoryProvider = "org.apache.openwhisk.core.containerpool.kubernetes.KubernetesContainerFactoryProvider"
+    LogStoreProvider = "org.apache.openwhisk.core.containerpool.logging.DockerToActivationLogStoreProvider"
+  }
+  kubernetes {
+    timeouts {
+      # Use higher timeout for run as in local dev the required Docker images may not be pre pulled
+      run = 10 minute
+      logs = 1 minute
+    }
+    user-pod-node-affinity {
+      enabled = false
+    }
+    port-forwarding-enabled = true
+  }
+}
diff --git a/core/standalone/src/main/scala/org/apache/openwhisk/standalone/StandaloneOpenWhisk.scala b/core/standalone/src/main/scala/org/apache/openwhisk/standalone/StandaloneOpenWhisk.scala
index e677faa..92ae3fc 100644
--- a/core/standalone/src/main/scala/org/apache/openwhisk/standalone/StandaloneOpenWhisk.scala
+++ b/core/standalone/src/main/scala/org/apache/openwhisk/standalone/StandaloneOpenWhisk.scala
@@ -51,7 +51,7 @@ class Conf(arguments: Seq[String]) extends ScallopConf(Conf.expandAllMode(argume
   this.printedName = "openwhisk"
   val configFile =
     opt[File](descr = "application.conf which overrides the default standalone.conf", validate = _.canRead)
-  val manifest = opt[File](descr = "Manifest json defining the supported runtimes", validate = _.canRead)
+  val manifest = opt[File](descr = "Manifest JSON defining the supported runtimes", validate = _.canRead)
   val port = opt[Int](descr = "Server port", default = Some(3233))
 
   val verbose = tally()
@@ -62,7 +62,7 @@ class Conf(arguments: Seq[String]) extends ScallopConf(Conf.expandAllMode(argume
   val devMode = opt[Boolean](
     descr = "Developer mode speeds up the startup by disabling preflight checks and avoiding explicit pulls.",
     noshort = true)
-  val apiGwPort = opt[Int](descr = "Api Gateway Port", default = Some(3234), noshort = true)
+  val apiGwPort = opt[Int](descr = "API Gateway Port", default = Some(3234), noshort = true)
   val dataDir = opt[File](descr = "Directory used for storage", default = Some(StandaloneOpenWhisk.defaultWorkDir))
 
   val kafka = opt[Boolean](descr = "Enable embedded Kafka support", noshort = true)
@@ -99,6 +99,8 @@ class Conf(arguments: Seq[String]) extends ScallopConf(Conf.expandAllMode(argume
     descr = "Enables all the optional services supported by Standalone OpenWhisk like CouchDB, Kafka etc",
     noshort = true)
 
+  val devKcf = opt[Boolean](descr = "Enables KubernetesContainerFactory for local development")
+
   mainOptions = Seq(manifest, configFile, apiGw, couchdb, userEvents, kafka, kafkaUi)
 
   verify()
@@ -270,7 +272,11 @@ object StandaloneOpenWhisk extends SLF4JLogging {
         require(f.exists(), s"Config file $f does not exist")
         System.setProperty("config.file", f.getAbsolutePath)
       case None =>
-        System.setProperty("config.resource", "standalone.conf")
+        val config = if (conf.devKcf()) {
+          "standalone-kcf.conf"
+        } else "standalone.conf"
+        System.setProperty("config.resource", config)
+
     }
   }