diff --git a/serving/samples/README.md b/serving/samples/README.md index 8c6211240..7e17b8fa3 100644 --- a/serving/samples/README.md +++ b/serving/samples/README.md @@ -7,7 +7,7 @@ more about Knative Serving resources. | Name | Description | Languages | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| Hello World | A quick introduction that highlights how to deploy an app using Knative Serving. | [C#](helloworld-csharp/README.md), [Go](helloworld-go/README.md), [Java](helloworld-java/README.md), [Kotlin](helloworld-kotlin/README.md), [Node.js](helloworld-nodejs/README.md), [PHP](helloworld-php/README.md), [Python](helloworld-python/README.md), [Ruby](helloworld-ruby/README.md) | +| Hello World | A quick introduction that highlights how to deploy an app using Knative Serving. | [C#](helloworld-csharp/README.md), [Go](helloworld-go/README.md), [Java](helloworld-java/README.md), [Kotlin](helloworld-kotlin/README.md), [Node.js](helloworld-nodejs/README.md), [PHP](helloworld-php/README.md), [Python](helloworld-python/README.md), [Ruby](helloworld-ruby/README.md), [Scala](helloworld-scala/README.md) | | Advanced Deployment | Simple blue/green-like application deployment pattern illustrating the process of updating a live application without dropping any traffic. | [YAML](blue-green-deployment.md) | | Autoscale | A demonstration of the autoscaling capabilities of Knative. | [Go](autoscale-go/README.md) | | Private Repo Build | An example of deploying a Knative Serving Service using a Github deploy-key and a DockerHub image pull secret. | [Go](build-private-repo-go/README.md) | diff --git a/serving/samples/helloworld-scala/.gitignore b/serving/samples/helloworld-scala/.gitignore new file mode 100644 index 000000000..eb5a316cb --- /dev/null +++ b/serving/samples/helloworld-scala/.gitignore @@ -0,0 +1 @@ +target diff --git a/serving/samples/helloworld-scala/README.md b/serving/samples/helloworld-scala/README.md new file mode 100644 index 000000000..1de8f1b1a --- /dev/null +++ b/serving/samples/helloworld-scala/README.md @@ -0,0 +1,136 @@ +# Hello World - Scala using Akka HTTP sample + +A microservice which demonstrates how to get set up and running with Knative Serving when using [Scala](https://scala-lang.org/) and [Akka](https://akka.io/) [HTTP](https://doc.akka.io/docs/akka-http/current/). It will respond to a HTTP request with a text specified as an `ENV` variable named `MESSAGE`, defaulting to `"Hello World!"`. + +## Prerequisites + +* A Kubernetes cluster [installation](https://github.com/knative/docs/blob/master/install/README.md) with Knative Serving up and running. +* [Docker](https://www.docker.com) installed locally, and running, optionally a Docker Hub account configured or some other Docker Repository installed locally. +* [Java JDK8 or later](https://adoptopenjdk.net/installation.html) installed locally. +* [Scala's](https://scala-lang.org/) standard build tool [sbt](https://www.scala-sbt.org/) installed locally. + +## Configuring the sbt build + +If you want to use your Docker Hub repository, set the repository to "docker.io/yourusername/yourreponame". + +If you use Minikube, you first need to run: + +```shell +eval $(minikube docker-env) +``` + +If want to use the Docker Repository inside Minikube, either set this to "dev.local" or if you want to use another repository name, then you need to run the following command after `docker:publishLocal`: + +```shell +docker tag yourreponame/helloworld-scala: dev.local/helloworld-scala: +``` + +Otherwise Knative Serving won't be able to resolve this image from the Minikube Docker Repository. + +You specify the repository in [build.sbt](build.sbt): + +```scala +dockerRepository := Some("your_repository_name") +``` + +You can learn more about the build configuration syntax [here](https://www.scala-sbt.org/1.x/docs/Basic-Def.html). + +## Configuring the Service descriptor + +Importantly, in [helloworld-scala.yaml](helloworld-scala.yaml) **change the image reference to match up with the repository**, name, and version specified in the [build.sbt](build.sbt) in the previous section. + +```yaml +apiVersion: serving.knative.dev/v1alpha1 +kind: Service +metadata: + name: helloworld-scala + namespace: default +spec: + runLatest: + configuration: + revisionTemplate: + spec: + container: + image: "your_repository_name/helloworld-scala:0.0.1" + imagePullPolicy: IfNotPresent + env: + - name: MESSAGE + value: "Scala & Akka on Knative says hello!" + - name: HOST + value: "localhost" + +``` + +## Publishing to Docker + +In order to build the project and create and push the Docker image, run either: + +```shell +sbt docker:publishLocal +``` + +or + +```shell +sbt docker:publish +``` + +Which of them to use is depending on whether you are publishing to a remote or a local Docker Repository. + +## Deploying to Knative Serving + +Locate the Knative Serving gateway address: + +```shell +# In Knative 0.2.x and prior versions, `knative-ingressgateway` service was used instead of `istio-ingressgateway`. + +kubectl get svc istio-ingressgateway --namespace istio-system +``` + +Example output, see the address under **EXTERNAL-IP**: + +```shell +NAME TYPE CLUSTER-IP EXTERNAL-IP PORTS) AGE +xxxxxxx-ingressgateway LoadBalancer 123.456.789.01 111.111.111.111 80:32380/TCP,443:32390/TCP,32400:32400/TCP 1m +``` + +Then export the external address obtained for ease of reuse later: + +```shell +export SERVING_GATEWAY= +``` + +If you use Minikube, then you will likely have to do the following instead: + +```shell +export SERVING_GATEWAY=$(minikube ip):$(kubectl get svc istio-ingressgateway --namespace istio-system --output 'jsonpath={.spec.ports[?(@.port==80)].nodePort}') +``` + +Apply the [Service yaml definition](helloworld-scala.yaml): + +```shell +kubectl apply --filename helloworld-scala.yaml +``` + +Then find the service host: + +```shell +kubectl get ksvc helloworld-scala \ + --output=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain + +# It will print something like this, the DOMAIN is what you're going to use as HTTP Host header: +# NAME DOMAIN +# helloworld-scala helloworld-scala.default.example.com +``` + +Finally, to try your service, use the obtained address in the Host header: + +```shell +curl -v -H "Host: helloworld-scala.default.example.com" http://$SERVING_GATEWAY +``` + +## Cleanup + +```shell +kubetl delete --filename helloworld-scala.yaml +``` \ No newline at end of file diff --git a/serving/samples/helloworld-scala/build.sbt b/serving/samples/helloworld-scala/build.sbt new file mode 100644 index 000000000..d9f316514 --- /dev/null +++ b/serving/samples/helloworld-scala/build.sbt @@ -0,0 +1,54 @@ +enablePlugins(JavaAppPackaging, DockerPlugin) + +organization := "Scala & Akka Examples for Knative Serving" + +name := "helloworld-scala" + +version := "0.0.1" + +scalaVersion := "2.12.8" + +mainClass in Compile := Some("klang.HelloWorldScala") + +scalacOptions ++= Seq("-encoding", "UTF-8") + +lazy val akkaVersion = "2.5.16" +lazy val akkaHttpVersion = "10.1.5" + +libraryDependencies ++= Seq( + "com.typesafe.akka" %% "akka-actor" % akkaVersion, + "com.typesafe.akka" %% "akka-stream" % akkaVersion, + "com.typesafe.akka" %% "akka-http" % akkaHttpVersion) + +// Make sure that the application.conf and possibly other resources are included + +import NativePackagerHelper._ + +mappings in Universal ++= directory( baseDirectory.value / "src" / "main" / "resources" ) + +// Inherit the package name +packageName in Docker := packageName.value + +// Inherit the version +version in Docker := version.value + +// Change this if you are changing which port to use in the app +dockerExposedPorts := Seq(8080) + +// You can specify some other jdk Docker image here: +dockerBaseImage := "openjdk" + +// If you want to supply specific JVM parameters, do that here: +// javaOptions in Universal ++= Seq() + +// If you change this, set this to an email address in the format of: "Name " +maintainer := "" + +// To use your Docker Hub repository set this to "docker.io/yourusername/yourreponame". +// When using Minikube Docker Repository set it to "dev.local", if you set it to anything else +// then run the following command after `docker:publishLocal`: +// `docker tag yourreponame/helloworld-scala: dev.local/helloworld-scala:` +dockerRepository := Some("your_repository_name") + +// For more information about which Docker configuration options are available, +// see: https://www.scala-sbt.org/sbt-native-packager/formats/docker.html diff --git a/serving/samples/helloworld-scala/helloworld-scala.yaml b/serving/samples/helloworld-scala/helloworld-scala.yaml new file mode 100644 index 000000000..70faaf556 --- /dev/null +++ b/serving/samples/helloworld-scala/helloworld-scala.yaml @@ -0,0 +1,18 @@ +apiVersion: serving.knative.dev/v1alpha1 +kind: Service +metadata: + name: helloworld-scala + namespace: default +spec: + runLatest: + configuration: + revisionTemplate: + spec: + container: + image: "your_repository_name/helloworld-scala:0.0.1" + imagePullPolicy: IfNotPresent + env: + - name: MESSAGE + value: "Scala & Akka on Knative says hello!" + - name: HOST + value: "localhost" diff --git a/serving/samples/helloworld-scala/project/build.properties b/serving/samples/helloworld-scala/project/build.properties new file mode 100644 index 000000000..0cd8b0798 --- /dev/null +++ b/serving/samples/helloworld-scala/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.3 diff --git a/serving/samples/helloworld-scala/project/plugins.sbt b/serving/samples/helloworld-scala/project/plugins.sbt new file mode 100644 index 000000000..95761233f --- /dev/null +++ b/serving/samples/helloworld-scala/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.16") diff --git a/serving/samples/helloworld-scala/src/main/resources/application.conf b/serving/samples/helloworld-scala/src/main/resources/application.conf new file mode 100644 index 000000000..d85dbb527 --- /dev/null +++ b/serving/samples/helloworld-scala/src/main/resources/application.conf @@ -0,0 +1,8 @@ +helloworld { + host = localhost + host = ${?HOST} + port = 8080 + port = ${?PORT} + message = "Hello World!" + message = ${?MESSAGE} +} diff --git a/serving/samples/helloworld-scala/src/main/scala/klang/HelloWorldScala.scala b/serving/samples/helloworld-scala/src/main/scala/klang/HelloWorldScala.scala new file mode 100644 index 000000000..feeaf0f05 --- /dev/null +++ b/serving/samples/helloworld-scala/src/main/scala/klang/HelloWorldScala.scala @@ -0,0 +1,51 @@ +package klang + +import akka.actor.ActorSystem +import akka.event.LoggingAdapter +import akka.http.scaladsl.Http +import akka.http.scaladsl.model._ +import ContentTypes._ +import akka.http.scaladsl.server.Directives._ +import akka.stream.{ActorMaterializer, Materializer} +import scala.concurrent.ExecutionContext +import scala.util.{Failure, Success} + + +object HelloWorldScala { + def main(args: Array[String]): Unit = { + // Creates and initializes an Akka Actor System + implicit val system: ActorSystem = ActorSystem("HelloWorldScala") + // Creates and initializes a Materializer to be used for the Akka HTTP Server + implicit val mat: Materializer = ActorMaterializer() + // Specifies where any Futures in this code will execute + implicit val ec: ExecutionContext = system.dispatcher + // Obtains a logger to be used for the sample + val log: LoggingAdapter = system.log + // Obtains a reference to the configuration for this application + val config = system.settings.config + + // These are read from the application.conf file under `resources` + val message = config.getString("helloworld.message") + val host = config.getString("helloworld.host") + val port = config.getInt("helloworld.port") + + // Here we define the endpoints exposed by this application + val serviceRoute = + path("") { + get { + log.info("Received request to HelloWorldScala") + complete(HttpEntity(`text/html(UTF-8)`, message)) + } + } + + // Here we create the Http server, and bind it to the host and the port, + // so we can serve requests using the route(s) we defined previously. + val binding = Http().bindAndHandle(serviceRoute, host, port) andThen { + case Success(sb) => + log.info("Bound: {}", sb) + case Failure(t) => + log.error(t, "Failed to bind to {}:{}—shutting down", host, port) + system.terminate() + } + } +}