diff --git a/examples/ambassador/README.md b/examples/ambassador/README.md new file mode 100644 index 0000000000..b1d37c9202 --- /dev/null +++ b/examples/ambassador/README.md @@ -0,0 +1,113 @@ +# Ambassador example project + +This example project demonstrates how to use the [Ambassador API Gateway](https://www.getambassador.io/) instead of the default Nginx ingress controller. Ambassador is an open source, Kubernetes-Native, API Gateway built on the [Envoy Proxy](https://www.envoyproxy.io/). Services are configured via [annotations](https://docs.garden.io/reference/module-types/container#module-services-annotations) which Ambassador reads to configure its Envoy Proxy. + +Even though we chose Ambassador for this project, the same principles apply to e.g. [Traefik](https://traefik.io/), which also supports configuring route mappings via service annotations. + +The project is based on our [simple-project example](https://github.com/garden-io/garden/tree/v0.9.0-docfix.2/examples/simple-project) and installs Ambassador via the [Helm module type](https://docs.garden.io/reference/module-types/helm). To learn more about using Helm charts with Garden, take a look at our [Helm user guide](https://docs.garden.io/using-garden/using-helm-charts). + +## Usage + +This project doesn't require any specific set up and can be deployed in a single step with the `deploy` command: + +```sh +garden deploy +``` + +Since we're letting Ambassador handle our ingresses, we don't define any in our `garden.yml` config files. Therefore, the `call` command won't work with this set up. Instead, we can use `curl` to check on our services. + +To find the external IP for the Ambassador service, run: + +```sh +kubectl get svc ambassador --namespace=custom-ingress-controller +``` + +It should return something like: + +```sh +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +ambassador LoadBalancer 10.102.14.233 localhost 8080:30634/TCP,443:30614/TCP 120m +``` + +Now we can call our services with: + + +```sh +curl localhost:8080/node-service/hello-node +``` + +which should return + +```sh +Hello from Node service! +``` + +## Notes on configuration + +### Project configuration + +If you've looked at our other examples, the project configuration should look familiar with the exception of the `setupIngressController` key: + +```yaml +project: + name: custom-ingress-controller + environments: + - name: local + providers: + - name: local-kubernetes + setupIngressController: false +``` + +The `setupIngressController` key is specific to the `local-kubernetes` plugin. Setting it to `false` disables the default Nginx ingress controller. + +### Ambassador configuration + +We've configured the Ambassador service to listen on port `8080` since the default Nginx ingress controller might occupy the default port (`80`) if we're running other Garden projects. Here's the relevant configuration from `ambassador/garden.yml`: + +```yaml +module: + description: Ambassador API Gateway + type: helm + name: ambassador + chart: stable/ambassador + values: + service: + annotations: + getambassador.io/config: | + --- + apiVersion: ambassador/v1 + kind: Module + name: ambassador + config: + service_port: 8080 # Set port since the default ingress already occupies the default port + http: + port: 8080 # Set port since the default ingress already occupies the default port +``` + +### Module configuration + +The module configuration is the same as for the `simple-project` example with the exception of annotations. Below is the configuration for our `go-service`: + +```yaml +module: + name: go-service + description: Go service container + type: container + services: + - name: go-service + ports: + - name: http + containerPort: 8080 + # Maps service:80 -> container:8080 + servicePort: 80 + annotations: + getambassador.io/config: | + --- + apiVersion: ambassador/v1 + kind: Mapping + name: go-service_mapping + prefix: /go-service/ + service: go-service:80 +``` + +Please refer to the [official Ambassador docs](https://www.getambassador.io/reference/mappings/) for more information on how to configure route mappings. diff --git a/examples/ambassador/ambassador/garden.yml b/examples/ambassador/ambassador/garden.yml new file mode 100644 index 0000000000..4dc22a5162 --- /dev/null +++ b/examples/ambassador/ambassador/garden.yml @@ -0,0 +1,17 @@ +module: + description: Ambassador API Gateway + type: helm + name: ambassador + chart: stable/ambassador + values: + service: + annotations: + getambassador.io/config: | + --- + apiVersion: ambassador/v1 + kind: Module + name: ambassador + config: + service_port: 8080 # Set port since the default ingress already occupies the default port + http: + port: 8080 # Set port since the default ingress already occupies the default port \ No newline at end of file diff --git a/examples/ambassador/garden.yml b/examples/ambassador/garden.yml new file mode 100644 index 0000000000..4e3dd30efa --- /dev/null +++ b/examples/ambassador/garden.yml @@ -0,0 +1,7 @@ +project: + name: ambassador + environments: + - name: local + providers: + - name: local-kubernetes + setupIngressController: false diff --git a/examples/ambassador/go-service/.dockerignore b/examples/ambassador/go-service/.dockerignore new file mode 100644 index 0000000000..1cd4736667 --- /dev/null +++ b/examples/ambassador/go-service/.dockerignore @@ -0,0 +1,4 @@ +node_modules +Dockerfile +garden.yml +app.yaml diff --git a/examples/ambassador/go-service/.gitignore b/examples/ambassador/go-service/.gitignore new file mode 100644 index 0000000000..eb086d61c3 --- /dev/null +++ b/examples/ambassador/go-service/.gitignore @@ -0,0 +1,27 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +.vscode/settings.json +webserver/*server* diff --git a/examples/ambassador/go-service/Dockerfile b/examples/ambassador/go-service/Dockerfile new file mode 100644 index 0000000000..eca3125f6e --- /dev/null +++ b/examples/ambassador/go-service/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.8.3-alpine +MAINTAINER Aurelien PERRIER + +ENV webserver_path /go/src/github.com/perriea/webserver/ +ENV PATH $PATH:$webserver_path + +WORKDIR $webserver_path +COPY webserver/ . + +RUN go build . + +ENTRYPOINT ./webserver + +EXPOSE 8080 diff --git a/examples/ambassador/go-service/garden.yml b/examples/ambassador/go-service/garden.yml new file mode 100644 index 0000000000..9a9459faa5 --- /dev/null +++ b/examples/ambassador/go-service/garden.yml @@ -0,0 +1,19 @@ +module: + name: go-service + description: Go service container + type: container + services: + - name: go-service + ports: + - name: http + containerPort: 8080 + # Maps service:80 -> container:8080 + servicePort: 80 + annotations: + getambassador.io/config: | + --- + apiVersion: ambassador/v1 + kind: Mapping + name: go-service_mapping + prefix: /go-service/ + service: go-service:80 diff --git a/examples/ambassador/go-service/webserver/main.go b/examples/ambassador/go-service/webserver/main.go new file mode 100644 index 0000000000..3bdee587e9 --- /dev/null +++ b/examples/ambassador/go-service/webserver/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "fmt" + "net/http" +) + +func handler(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Hello from Go!") +} + +func main() { + http.HandleFunc("/hello-go", handler) + fmt.Println("Server running...") + + http.ListenAndServe(":8080", nil) +} diff --git a/examples/ambassador/node-service/.dockerignore b/examples/ambassador/node-service/.dockerignore new file mode 100644 index 0000000000..1cd4736667 --- /dev/null +++ b/examples/ambassador/node-service/.dockerignore @@ -0,0 +1,4 @@ +node_modules +Dockerfile +garden.yml +app.yaml diff --git a/examples/ambassador/node-service/Dockerfile b/examples/ambassador/node-service/Dockerfile new file mode 100644 index 0000000000..4a418d7d1e --- /dev/null +++ b/examples/ambassador/node-service/Dockerfile @@ -0,0 +1,12 @@ +FROM node:9-alpine + +ENV PORT=8080 +EXPOSE ${PORT} +WORKDIR /app + +ADD package.json /app +RUN npm install + +ADD . /app + +CMD ["npm", "start"] diff --git a/examples/ambassador/node-service/app.js b/examples/ambassador/node-service/app.js new file mode 100644 index 0000000000..27138fb1c6 --- /dev/null +++ b/examples/ambassador/node-service/app.js @@ -0,0 +1,28 @@ +const express = require('express'); +const request = require('request-promise') +const app = express(); + +// Unless configured otherwise, the hostname is simply the service name +const goServiceEndpoint = `http://go-service/hello-go`; + +app.get('/hello-node', (req, res) => res.send('Hello from Node service!')); + +app.get('/call-go-service', (req, res) => { + // Query the go-service and return the response + request.get(goServiceEndpoint) + .then(message => { + message = `Go says: '${message}'` + res.json({ + message, + }) + }) + .catch(err => { + res.statusCode = 500 + res.json({ + error: err, + message: "Unable to reach service at " + goServiceEndpoint, + }) + }); +}); + +module.exports = { app } diff --git a/examples/ambassador/node-service/garden.yml b/examples/ambassador/node-service/garden.yml new file mode 100644 index 0000000000..cb05ff889a --- /dev/null +++ b/examples/ambassador/node-service/garden.yml @@ -0,0 +1,26 @@ +module: + name: node-service + description: Node service container + type: container + services: + - name: node-service + ports: + - name: http + containerPort: 8080 + annotations: + getambassador.io/config: | + --- + apiVersion: ambassador/v1 + kind: Mapping + name: node-service_mapping + prefix: /node-service/ + service: node-service:8080 + dependencies: + - go-service + tests: + - name: unit + args: [npm, test] + - name: integ + args: [npm, run, integ] + dependencies: + - go-service diff --git a/examples/ambassador/node-service/main.js b/examples/ambassador/node-service/main.js new file mode 100644 index 0000000000..06833ec64f --- /dev/null +++ b/examples/ambassador/node-service/main.js @@ -0,0 +1,3 @@ +const { app } = require('./app'); + +app.listen(process.env.PORT, '0.0.0.0', () => console.log('Node service started')); diff --git a/examples/ambassador/node-service/package.json b/examples/ambassador/node-service/package.json new file mode 100644 index 0000000000..790546d484 --- /dev/null +++ b/examples/ambassador/node-service/package.json @@ -0,0 +1,22 @@ +{ + "name": "node-service", + "version": "1.0.0", + "description": "Simple Node.js docker service", + "main": "main.js", + "scripts": { + "start": "node main.js", + "test": "echo OK", + "integ": "node_modules/mocha/bin/mocha test/integ.js" + }, + "author": "garden.io ", + "license": "ISC", + "dependencies": { + "express": "^4.16.2", + "request": "^2.83.0", + "request-promise": "^4.2.2" + }, + "devDependencies": { + "mocha": "^5.1.1", + "supertest": "^3.0.0" + } +} diff --git a/examples/ambassador/node-service/test/integ.js b/examples/ambassador/node-service/test/integ.js new file mode 100644 index 0000000000..580cea650f --- /dev/null +++ b/examples/ambassador/node-service/test/integ.js @@ -0,0 +1,17 @@ +const supertest = require("supertest") +const { app } = require("../app") + +describe('GET /call-go-service', () => { + const agent = supertest.agent(app) + + it('should respond with a message from go-service', (done) => { + agent + .get("/call-go-service") + .expect(200, { message: "Go says: 'Hello from Go!'" }) + .end((err) => { + if (err) return done(err) + done() + }) + }) +}) +