From 6fee14709fb3091638f1dc34314b69fcf7535769 Mon Sep 17 00:00:00 2001 From: averikitsch Date: Mon, 12 Aug 2019 14:40:56 -0700 Subject: [PATCH 1/3] draft --- .../serving/helloworld-rserver/Dockerfile | 8 + .../serving/helloworld-rserver/HelloWorld.R | 5 + .../serving/helloworld-rserver/README.md | 205 ++++++++++++++++++ .../serving/helloworld-rserver/service.yaml | 13 ++ 4 files changed, 231 insertions(+) create mode 100644 community/samples/serving/helloworld-rserver/Dockerfile create mode 100644 community/samples/serving/helloworld-rserver/HelloWorld.R create mode 100644 community/samples/serving/helloworld-rserver/README.md create mode 100644 community/samples/serving/helloworld-rserver/service.yaml diff --git a/community/samples/serving/helloworld-rserver/Dockerfile b/community/samples/serving/helloworld-rserver/Dockerfile new file mode 100644 index 00000000000..43f531872e1 --- /dev/null +++ b/community/samples/serving/helloworld-rserver/Dockerfile @@ -0,0 +1,8 @@ +# The official R base image +# https://hub.docker.com/_/r-base +FROM r-base:3.6.0 + +COPY HelloWorld.R . + +# Run the web service on container startup. +CMD ["/invoke"] diff --git a/community/samples/serving/helloworld-rserver/HelloWorld.R b/community/samples/serving/helloworld-rserver/HelloWorld.R new file mode 100644 index 00000000000..eddb231cc47 --- /dev/null +++ b/community/samples/serving/helloworld-rserver/HelloWorld.R @@ -0,0 +1,5 @@ +#!/bin/Rscript +TARGET <- Sys.getenv("TARGET", "World") + +message = paste("Hello ", TARGET, "!", sep = "") +print(message) diff --git a/community/samples/serving/helloworld-rserver/README.md b/community/samples/serving/helloworld-rserver/README.md new file mode 100644 index 00000000000..a73e9c1833e --- /dev/null +++ b/community/samples/serving/helloworld-rserver/README.md @@ -0,0 +1,205 @@ +--- +title: "Hello World - R" +linkTitle: "R" +weight: 1 +type: "docs" +--- + +A simple web app that executes an R script. The R script reads an env +variable `TARGET` and prints `Hello ${TARGET}!`. If the `TARGET` environment +variable is not specified, the script uses `World`. + +Follow the steps below to create the sample code and then deploy the app to your +cluster. You can also download a working copy of the sample, by running the +following commands: + +```shell +git clone -b "{{< branch >}}" https://github.com/knative/docs knative-docs +cd knative-docs/docs/serving/samples/hello-world/helloworld-r +``` + +## Before you begin + +- A Kubernetes cluster with Knative installed. Follow the + [installation instructions](../../../../install/README.md) if you need to + create one. +- [Docker](https://www.docker.com) installed and running on your local machine, + and a Docker Hub account configured (we'll use it for a container registry). + +## Recreating the sample code + +1. Create a new file named `HelloWorld.R` and paste the following script: + + ```R + #!/usr/bin/Rscript + TARGET <- Sys.getenv("TARGET", "World") + + message = paste("Hello ", TARGET, "!", sep = "") + print(message) + ``` + + 1. Create a new file named `invoke.go` and paste the following code. We use a + basic web server written in Go to execute the shell script: + + ```go + package main + + import ( + "fmt" + "log" + "net/http" + "os" + "os/exec" + ) + + func handler(w http.ResponseWriter, r *http.Request) { + cmd := exec.CommandContext(r.Context(), "Rscript", "HelloWorld.R") + cmd.Stderr = os.Stderr + out, err := cmd.Output() + if err != nil { + w.WriteHeader(500) + } + w.Write(out) + } + + func main() { + http.HandleFunc("/", handler) + + port := os.Getenv("PORT") + if port == "" { + port = "8080" + } + + log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil)) + } + ``` + + 1. Create a new file named `Dockerfile` and copy the code block below into it. + + ```docker + # Build executable binary + FROM golang:1.11 AS builder + + WORKDIR $GOPATH + + COPY invoke.go . + RUN go build -o /go/bin/invoke + + # Build r-base image + FROM r-base:3.6.0 + + # Copy Go binary + COPY --from=builder /go/bin/invoke /go/bin/invoke + COPY HelloWorld.R . + + ENTRYPOINT ["/go/bin/invoke"] + ``` + + +1. Create a new file, `service.yaml` and copy the following service definition + into the file. Make sure to replace `{username}` with your Docker Hub + username. + + ```yaml + apiVersion: serving.knative.dev/v1alpha1 + kind: Service + metadata: + name: helloworld-r + namespace: default + spec: + template: + spec: + containers: + - image: docker.io/{username}/helloworld-r + env: + - name: TARGET + value: "R Sample v1" + ``` + +## Building and deploying the sample + +Once you have recreated the sample code files (or used the files in the sample +folder) you're ready to build and deploy the sample app. + +1. Use Docker to build the sample code into a container. To build and push with + Docker Hub, run these commands replacing `{username}` with your Docker Hub + username: + + ```shell + # Build the container on your local machine + docker build -t {username}/helloworld-r . + + # Push the container to docker registry + docker push {username}/helloworld-r + ``` + +1. After the build has completed and the container is pushed to docker hub, you + can deploy the app into your cluster. Ensure that the container image value + in `service.yaml` matches the container you built in the previous step. Apply + the configuration using `kubectl`: + + ```shell + kubectl apply --filename service.yaml + ``` + +1. Now that your service is created, Knative performs the following steps: + + - Create a new immutable revision for this version of the app. + - Network programming to create a route, ingress, service, and load balance + for your app. + - Automatically scale your pods up and down (including to zero active pods). + +1. Run the following command to find the external IP address for your service. + The ingress IP for your cluster is returned. If you just created your + cluster, you might need to wait and rerun the command until your service gets + asssigned an external IP address. + + ```shell + kubectl get svc knative-ingressgateway --namespace istio-system + ``` + + Example: + + ```shell + NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE + knative-ingressgateway LoadBalancer 10.23.247.74 35.203.155.229 80:32380/TCP,443:32390/TCP,32400:32400/TCP 2d + + ``` + +1. Run the following command to find the domain URL for your service: + + ```shell + kubectl get ksvc helloworld-r --output=custom-columns=NAME:.metadata.name,URL:.status.url + ``` + + Example: + + ```shell + NAME URL + helloworld-r http://helloworld-r.default.example.com + ``` + +1. Test your app by sending it a request. Use the following `curl` command with + the domain URL `helloworld-r.default.example.com` and `EXTERNAL-IP` + address that you retrieved in the previous steps: + + ```shell + curl -H "Host: helloworld-r.default.example.com" http://{EXTERNAL_IP_ADDRESS} + ``` + + Example: + + ```shell + curl -H "Host: helloworld-r.default.example.com" http://35.203.155.229 + [1] "Hello R Sample v1!" + ``` + + > Note: Add `-v` option to get more detail if the `curl` command failed. + +## Removing the sample app deployment + +To remove the sample app from your cluster, delete the service record: + +```shell +kubectl delete --filename service.yaml +``` diff --git a/community/samples/serving/helloworld-rserver/service.yaml b/community/samples/serving/helloworld-rserver/service.yaml new file mode 100644 index 00000000000..05530c3d5e6 --- /dev/null +++ b/community/samples/serving/helloworld-rserver/service.yaml @@ -0,0 +1,13 @@ +apiVersion: serving.knative.dev/v1alpha1 +kind: Service +metadata: + name: helloworld-r + namespace: default +spec: + template: + spec: + containers: + - image: docker.io/{username}/helloworld-r + env: + - name: TARGET + value: "R Sample v1" From 8b559f849e25beec2795706c96afa34344354b0d Mon Sep 17 00:00:00 2001 From: averikitsch Date: Mon, 26 Aug 2019 11:08:18 -0700 Subject: [PATCH 2/3] R plumber sample --- .../serving/helloworld-rserver/Dockerfile | 9 +- .../serving/helloworld-rserver/HelloWorld.R | 12 +- .../serving/helloworld-rserver/README.md | 115 ++++++++---------- .../serving/helloworld-rserver/server.R | 8 ++ .../serving/helloworld-rserver/service.yaml | 6 +- 5 files changed, 74 insertions(+), 76 deletions(-) create mode 100644 community/samples/serving/helloworld-rserver/server.R diff --git a/community/samples/serving/helloworld-rserver/Dockerfile b/community/samples/serving/helloworld-rserver/Dockerfile index 43f531872e1..3b3c450ec18 100644 --- a/community/samples/serving/helloworld-rserver/Dockerfile +++ b/community/samples/serving/helloworld-rserver/Dockerfile @@ -2,7 +2,12 @@ # https://hub.docker.com/_/r-base FROM r-base:3.6.0 -COPY HelloWorld.R . +# Copy local code to the container image. +WORKDIR /usr/src/app +COPY . . + +# Install R packages +RUN Rscript -e "install.packages('plumber', repos='http://cran.us.r-project.org/')" # Run the web service on container startup. -CMD ["/invoke"] +CMD ["Rscript", "server.R"] diff --git a/community/samples/serving/helloworld-rserver/HelloWorld.R b/community/samples/serving/helloworld-rserver/HelloWorld.R index eddb231cc47..f09d12f34e3 100644 --- a/community/samples/serving/helloworld-rserver/HelloWorld.R +++ b/community/samples/serving/helloworld-rserver/HelloWorld.R @@ -1,5 +1,9 @@ -#!/bin/Rscript -TARGET <- Sys.getenv("TARGET", "World") +#' HelloWorld function +#' @get / +#' @html +function() { + TARGET <- Sys.getenv("TARGET", "World") -message = paste("Hello ", TARGET, "!", sep = "") -print(message) + message = paste("Hello ", TARGET, "!", sep = "") + print(message) +} diff --git a/community/samples/serving/helloworld-rserver/README.md b/community/samples/serving/helloworld-rserver/README.md index a73e9c1833e..fdef8cc3280 100644 --- a/community/samples/serving/helloworld-rserver/README.md +++ b/community/samples/serving/helloworld-rserver/README.md @@ -1,13 +1,14 @@ --- -title: "Hello World - R" -linkTitle: "R" +title: "Hello World - R server" +linkTitle: "R server" weight: 1 type: "docs" --- -A simple web app that executes an R script. The R script reads an env -variable `TARGET` and prints `Hello ${TARGET}!`. If the `TARGET` environment -variable is not specified, the script uses `World`. +A simple web app created with R package, [plumber](https://www.rplumber.io). +plumber creates a REST API by adding annotations to your R code. The R script +reads an environment variable `TARGET` and prints `Hello ${TARGET}!`. If the +`TARGET` environment variable is not specified, the script uses `World`. Follow the steps below to create the sample code and then deploy the app to your cluster. You can also download a working copy of the sample, by running the @@ -31,68 +32,48 @@ cd knative-docs/docs/serving/samples/hello-world/helloworld-r 1. Create a new file named `HelloWorld.R` and paste the following script: ```R - #!/usr/bin/Rscript - TARGET <- Sys.getenv("TARGET", "World") - - message = paste("Hello ", TARGET, "!", sep = "") - print(message) + #' HelloWorld function + #' @get / + #' @html + function() { + TARGET <- Sys.getenv("TARGET", "World") + + message = paste("Hello ", TARGET, "!", sep = "") + print(message) + } ``` - 1. Create a new file named `invoke.go` and paste the following code. We use a - basic web server written in Go to execute the shell script: - - ```go - package main - - import ( - "fmt" - "log" - "net/http" - "os" - "os/exec" - ) - - func handler(w http.ResponseWriter, r *http.Request) { - cmd := exec.CommandContext(r.Context(), "Rscript", "HelloWorld.R") - cmd.Stderr = os.Stderr - out, err := cmd.Output() - if err != nil { - w.WriteHeader(500) - } - w.Write(out) - } - - func main() { - http.HandleFunc("/", handler) - - port := os.Getenv("PORT") - if port == "" { - port = "8080" - } - - log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil)) - } - ``` + This file defines the endpoint `/`, using plumber annotations. - 1. Create a new file named `Dockerfile` and copy the code block below into it. + 1. Create a new file named `server.R` and paste the following code: - ```docker - # Build executable binary - FROM golang:1.11 AS builder + ```R + library(plumber) # https://www.rplumber.io/ - WORKDIR $GOPATH + # Translate the HelloWorld file into a Plumber API + r <- plumb("HelloWorld.R") + # Get the PORT env var + PORT <- strtoi(Sys.getenv("PORT", 8080)) + # Run the API + r$run(port=PORT, host="0.0.0.0") + ``` - COPY invoke.go . - RUN go build -o /go/bin/invoke + 1. Create a new file named `Dockerfile` and paste the following code: - # Build r-base image + ```docker + # The official R base image + # https://hub.docker.com/_/r-base FROM r-base:3.6.0 - # Copy Go binary - COPY --from=builder /go/bin/invoke /go/bin/invoke - COPY HelloWorld.R . + # Copy local code to the container image. + WORKDIR /usr/src/app + COPY . . + + # Install R packages + RUN Rscript -e "install.packages('plumber', repos='http://cran.us.r-project.org/')" - ENTRYPOINT ["/go/bin/invoke"] + # Run the web service on container startup. + CMD ["Rscript", "server.R"] ``` @@ -104,16 +85,16 @@ cd knative-docs/docs/serving/samples/hello-world/helloworld-r apiVersion: serving.knative.dev/v1alpha1 kind: Service metadata: - name: helloworld-r + name: helloworld-rserver namespace: default spec: template: spec: containers: - - image: docker.io/{username}/helloworld-r - env: - - name: TARGET - value: "R Sample v1" + - image: docker.io/{username}/helloworld-rserver + env: + - name: TARGET + value: "R Server Sample v1" ``` ## Building and deploying the sample @@ -127,10 +108,10 @@ folder) you're ready to build and deploy the sample app. ```shell # Build the container on your local machine - docker build -t {username}/helloworld-r . + docker build -t {username}/helloworld-rserver . # Push the container to docker registry - docker push {username}/helloworld-r + docker push {username}/helloworld-rserver ``` 1. After the build has completed and the container is pushed to docker hub, you @@ -180,17 +161,17 @@ folder) you're ready to build and deploy the sample app. ``` 1. Test your app by sending it a request. Use the following `curl` command with - the domain URL `helloworld-r.default.example.com` and `EXTERNAL-IP` + the domain URL `helloworld-rserver.default.example.com` and `EXTERNAL-IP` address that you retrieved in the previous steps: ```shell - curl -H "Host: helloworld-r.default.example.com" http://{EXTERNAL_IP_ADDRESS} + curl -H "Host: helloworld-rserver.default.example.com" http://{EXTERNAL_IP_ADDRESS} ``` Example: ```shell - curl -H "Host: helloworld-r.default.example.com" http://35.203.155.229 + curl -H "Host: helloworld-rserver.default.example.com" http://35.203.155.229 [1] "Hello R Sample v1!" ``` diff --git a/community/samples/serving/helloworld-rserver/server.R b/community/samples/serving/helloworld-rserver/server.R new file mode 100644 index 00000000000..5c05dba0f2b --- /dev/null +++ b/community/samples/serving/helloworld-rserver/server.R @@ -0,0 +1,8 @@ +library(plumber) # https://www.rplumber.io/ + +# Translate the HelloWorld file into a Plumber API +r <- plumb("HelloWorld.R") +# Get the PORT env var +PORT <- strtoi(Sys.getenv("PORT", 8080)) +# Run the API +r$run(port=PORT, host="0.0.0.0") diff --git a/community/samples/serving/helloworld-rserver/service.yaml b/community/samples/serving/helloworld-rserver/service.yaml index 05530c3d5e6..dbf64914ef8 100644 --- a/community/samples/serving/helloworld-rserver/service.yaml +++ b/community/samples/serving/helloworld-rserver/service.yaml @@ -1,13 +1,13 @@ apiVersion: serving.knative.dev/v1alpha1 kind: Service metadata: - name: helloworld-r + name: helloworld-rserver namespace: default spec: template: spec: containers: - - image: docker.io/{username}/helloworld-r + - image: docker.io/{username}/helloworld-rserver env: - name: TARGET - value: "R Sample v1" + value: "R Server Sample v1" From 3f92f11c1e826f96cd15adae159d0541404834d7 Mon Sep 17 00:00:00 2001 From: averikitsch Date: Fri, 6 Sep 2019 12:14:52 -0700 Subject: [PATCH 3/3] update Readme --- community/samples/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/community/samples/README.md b/community/samples/README.md index 898cccf4240..106bfa7f8f3 100644 --- a/community/samples/README.md +++ b/community/samples/README.md @@ -12,9 +12,9 @@ something isn't working, lend a helping hand and fix it in a PR. Knative Serving sample apps. -| Sample Name | Description | Language(s) | -| ----------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Hello World | A quick introduction to Knative Serving that highlights how to deploy an app. | [Clojure](./serving/helloworld-clojure/README.md), [Dart](./serving/helloworld-dart/README.md), [Elixir](./serving/helloworld-elixir/README.md), [Haskell](./serving/helloworld-haskell/README.md), [Java - Micronaut](./serving/helloworld-java-micronaut/README.md), [Java - Quarkus](./serving/helloworld-java-quarkus/README.md), [Rust](./serving/helloworld-rust/README.md), [Swift](./serving/helloworld-swift/README.md), [Vertx](./serving/helloworld-vertx/README.md) | +| Sample Name | Description | Language(s) | +| ----------- | ----------- | ----------- | +| Hello World | A quick introduction to Knative Serving that highlights how to deploy an app. | [Clojure](./serving/helloworld-clojure/README.md), [Dart](./serving/helloworld-dart/README.md), [Elixir](./serving/helloworld-elixir/README.md), [Haskell](./serving/helloworld-haskell/README.md), [Java - Micronaut](./serving/helloworld-java-micronaut/README.md), [Java - Quarkus](./serving/helloworld-java-quarkus/README.md), [R - Go Server](./serving/helloworld-r/README.md), [R](./serving/hellowolrd-rserver/README.md), [Rust](./serving/helloworld-rust/README.md), [Swift](./serving/helloworld-swift/README.md), [Vertx](./serving/helloworld-vertx/README.md) | #### Eventing and Eventing Resources samples