diff --git a/advanced_examples/minikube-dist/.gitignore b/advanced_examples/minikube-dist/.gitignore new file mode 100644 index 0000000..172c657 --- /dev/null +++ b/advanced_examples/minikube-dist/.gitignore @@ -0,0 +1,2 @@ +_build/ +rebar.lock diff --git a/advanced_examples/minikube-dist/.travis.yml b/advanced_examples/minikube-dist/.travis.yml new file mode 100644 index 0000000..f5bea86 --- /dev/null +++ b/advanced_examples/minikube-dist/.travis.yml @@ -0,0 +1,51 @@ +sudo: required + +dist: xenial + +addons: + apt: + packages: + - curl + +env: +- CHANGE_MINIKUBE_NONE_USER=true + +before_script: +# Download minikube. +- MINIKUBE_VERSION=latest +- curl -Lo minikube https://storage.googleapis.com/minikube/releases/$MINIKUBE_VERSION/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/ +# Download kubectl, which is a requirement for using minikube. +- KUBERNETES_VERSION=$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt) +- curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/$KUBERNETES_VERSION/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/ +# Test that it works +- kubectl -h +- sudo minikube start -v 7 --logtostderr --vm-driver=none --kubernetes-version "$KUBERNETES_VERSION" +# Fix the kubectl context, as it's often stale. +- minikube update-context +# Wait for Kubernetes to be up and ready. +- JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}'; until kubectl get nodes -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do sleep 1; done + +script: +- kubectl cluster-info +# kube-addon-manager is responsible for managing other kubernetes components, such as kube-dns, dashboard, storage-provisioner.. +- JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}'; until kubectl -n kube-system get pods -lcomponent=kube-addon-manager -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do sleep 5;echo "waiting for kube-addon-manager to be available"; kubectl get pods --all-namespaces; done +# Wait for kube-dns to be ready. +- JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}'; until kubectl -n kube-system get pods -lk8s-app=kube-dns -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do sleep 5;echo "waiting for kube-dns to be available"; kubectl get pods --all-namespaces; done +- kubectl create service clusterip backend --tcp=12345:12345 +- docker build -t backend -f Dockerfile.backend . +- kubectl apply -f backend-deploy.yaml +- kubectl create service nodeport dockerwatch --tcp=8080:8080 --tcp=8443:8443 +- kubectl get service +- ./create-certs $(minikube ip) +- kubectl create secret generic dockerwatch --from-file=ssl/ +- kubectl get secret +- docker build -t dockerwatch . +- kubectl apply -f dockerwatch-deploy.yaml +# Wait for dockerwatch to be ready. +- JSONPATH='{range .items[*]}{@.metadata.name}:{@.status.readyReplicas};{end}'; until kubectl -n default get deployment -lapp=dockerwatch -o jsonpath="$JSONPATH" 2>&1 | grep -q ":10"; do sleep 5;echo "waiting for dockerwatch to be available"; kubectl get pods --all-namespaces; done +- HTTP=$(minikube service dockerwatch --url | head -1) +- HTTPS=$(minikube service dockerwatch --url --https | tail -1) +- "until curl -v -H 'Content-Type: application/json' -X POST -d '' $HTTP/cnt; do sleep 10; done" +- "curl -v -H 'Content-Type: application/json' -X POST -d '{}' $HTTP/cnt" +- "curl -v --cacert ssl/dockerwatch-ca.pem -H 'Accept: application/json' $HTTPS/" +- "curl -v --cacert ssl/dockerwatch-ca.pem -H 'Accept: application/json' $HTTPS/cnt" diff --git a/advanced_examples/minikube-dist/Docker-Cheat-Sheet.md b/advanced_examples/minikube-dist/Docker-Cheat-Sheet.md new file mode 100644 index 0000000..9d58795 --- /dev/null +++ b/advanced_examples/minikube-dist/Docker-Cheat-Sheet.md @@ -0,0 +1,26 @@ +## Docker Cheatsheet + +* Remove all containers that are not running: + + $ docker rm $(docker ps -aq -f status=exited) + +* Remove dangling images: + + $ docker rmi $(docker images -f dangling=true -q) + +* Attach to running docker: + + $ docker exec -i -t NameOrId /bin/sh + +## Core generation + + * `/proc/sys/core_pattern` is clearly persisted on the host. Taking note of + its content before starting any endeavour is therefore highly encouraged. + * dockers `--privileged` is necessary for a gdb session to catch the stack, + without privileges, gdb just complains about No stack. Google still is + hardly knowledgeable about this phenomenon... + * setting ulimit on docker run works perfectly, for future googlers (syntax hard to find), + a docker-compose example: + + ulimits: + core: -1 diff --git a/advanced_examples/minikube-dist/Dockerfile b/advanced_examples/minikube-dist/Dockerfile new file mode 100644 index 0000000..e2f1c7e --- /dev/null +++ b/advanced_examples/minikube-dist/Dockerfile @@ -0,0 +1,37 @@ +# Build stage 0 +FROM erlang:alpine + +# Install Rebar3 +RUN mkdir -p /buildroot/rebar3/bin +ADD https://s3.amazonaws.com/rebar3/rebar3 /buildroot/rebar3/bin/rebar3 +RUN chmod a+x /buildroot/rebar3/bin/rebar3 + +# Setup Environment +ENV PATH=/buildroot/rebar3/bin:$PATH + +# Reset working directory +WORKDIR /buildroot + +# Copy our Erlang test application +COPY dockerwatch dockerwatch + +# And build the release +WORKDIR dockerwatch +RUN rebar3 as prod release + + +# Build stage 1 +FROM alpine + +# Install some libs +RUN apk add --no-cache openssl && \ + apk add --no-cache ncurses-libs + +# Install the released application +COPY --from=0 /buildroot/dockerwatch/_build/prod/rel/dockerwatch /dockerwatch + +# Expose relevant ports +EXPOSE 8080 +EXPOSE 8443 + +CMD ["/dockerwatch/bin/dockerwatch", "foreground"] diff --git a/advanced_examples/minikube-dist/Dockerfile.backend b/advanced_examples/minikube-dist/Dockerfile.backend new file mode 100644 index 0000000..130481e --- /dev/null +++ b/advanced_examples/minikube-dist/Dockerfile.backend @@ -0,0 +1,37 @@ +# Build stage 0 +FROM erlang:alpine + +# Install Rebar3 +RUN mkdir -p /buildroot/rebar3/bin +ADD https://s3.amazonaws.com/rebar3/rebar3 /buildroot/rebar3/bin/rebar3 +RUN chmod a+x /buildroot/rebar3/bin/rebar3 + +# Setup Environment +ENV PATH=/buildroot/rebar3/bin:$PATH + +# Reset working directory +WORKDIR /buildroot + +# Copy our Erlang test application +COPY backend backend + +# And build the release +WORKDIR backend +RUN rebar3 as prod release + + +# Build stage 1 +FROM alpine + +# Install some libs +RUN apk add --no-cache openssl && \ + apk add --no-cache ncurses-libs + +# Install the released application +COPY --from=0 /buildroot/backend/_build/prod/rel/backend /backend + +# Expose relevant ports +EXPOSE 8080 +EXPOSE 8443 + +CMD ["/backend/bin/backend", "foreground"] diff --git a/advanced_examples/minikube-dist/README-CERTS.md b/advanced_examples/minikube-dist/README-CERTS.md new file mode 100644 index 0000000..10b9565 --- /dev/null +++ b/advanced_examples/minikube-dist/README-CERTS.md @@ -0,0 +1,23 @@ +## Generating Certificate + +Generate certificates in subdirectory `ssl`. + +### Root CA + + $ openssl genrsa -out dockerwatch-ca.key 4096 + + $ openssl req -x509 -new -nodes -key dockerwatch-ca.key -sha256 -days 1024 -out dockerwatch-ca.pem + +### Server Certificate + + $ openssl genrsa -out dockerwatch-server.key 4096 + +Certificate signing request + + $ openssl req -new -key dockerwatch-server.key -out dockerwatch-server.csr + +The most important field: `Common Name (eg, YOUR name) []: localhost`. We use localhost in this example. + +### Sign it + + $ openssl x509 -req -in dockerwatch-server.csr -CA dockerwatch-ca.pem -CAkey dockerwatch-ca.key -CAcreateserial -out dockerwatch-server.pem -days 500 -sha256 diff --git a/advanced_examples/minikube-dist/README.md b/advanced_examples/minikube-dist/README.md new file mode 100644 index 0000000..c4b0ae5 --- /dev/null +++ b/advanced_examples/minikube-dist/README.md @@ -0,0 +1,264 @@ +# Using Minikube and Erlang + +This is a quick demo of using minikube to run a distributed Erlang application. +The example we will use is the +[Docker Watch](http://github.com/erlang/docker-erlang-example/tree/master) +node as a base. + +This is only meant to be an example of how to get started. It is not the only, +nor neccesarily the best way to setup minikube with distributed Erlang. + +# Other Demos + +* [Using Docker](http://github.com/erlang/docker-erlang-example/) +* [Using Docker: Logstash](http://github.com/erlang/docker-erlang-example/tree/logstash) +* [Using Docker Compose: Logstash/ElasticSearch/Kibana](http://github.com/erlang/docker-erlang-example/tree/elk) +* [Using Minikube: Simple](http://github.com/erlang/docker-erlang-example/tree/minikube-simple) +* [Using Minikube: Prometheus/Grafana](http://github.com/erlang/docker-erlang-example/tree/minikube-prom-graf) +* [Using Minikube: Distributed Erlang](http://github.com/erlang/docker-erlang-example/tree/minikube-dist) +* [Using Minikube: Encrypted Distributed Erlang](http://github.com/erlang/docker-erlang-example/tree/minikube-tls-dist) + +# Prerequisites + +To start with you should familiarize yourself with minikube through this guide: +https://kubernetes.io/docs/setup/minikube/ + +In a nutshell: + +## Install + + * [VirtualBox](https://www.virtualbox.org/wiki/Downloads) + * [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) + * [minikube](https://github.com/kubernetes/minikube/releases) + +## Start and test + + > minikube start + > kubectl run hello-minikube --image=k8s.gcr.io/echoserver:1.10 --port=8080 + > kubectl expose deployment hello-minikube --type=NodePort + > curl $(minikube service hello-minikube --url) + ## Should print a lot of text + > kubectl delete services hello-minikube + > kubectl delete deployment hello-minikube + > minikube stop + +# Deploying Dockerwatch + +In this demo we will be doing three things: + +* Create a new application called backend for mnesia +* Modify dockerwatch to use mnesia as its storage +* Create a Service and Deployment for the backend +* Create a Deployment of dockerwatch that implements the Service + +First however, make sure that the minikube cluster is started: + + > minikube start + +and that you have cloned this repo and checked out this branch: + + > git clone https://github.com/erlang/docker-erlang-example + > cd docker-erlang-example + > git checkout minikube-dist + +## Create backend + +The purpose of the backend is to be the service responsible for writing +keeping the data. So the only thing it needs to do is some mnesia +initialization when [starting](backend/src/backend_app.erl). + +Since we are running inside a kluster where each pod gets its own IP address +we don't really need epmd any more. So in this example we use the `-epmd_module` +to implement our own static epmd client that always returns port 12345 as the +distribution port. The module looks like this: + +``` +-module(epmd_static). + +-export([start_link/0, register_node/2, register_node/3, + port_please/2, address_please/3]). +%% API. + +start_link() -> + ignore. + +register_node(Name, Port) -> + register_node(Name, Port, inet_tcp). +register_node(_Name, _Port, _Driver) -> + {ok, 0}. + +port_please(_Name, _Host) -> + {port, 12345, 5}. + +address_please(Name, Host, AddressFamily) -> + erl_epmd:address_please(Name, Host, AddressFamily). +``` + +The module is then configured to be used in [vm.args.src](backend/config/vm.args.src): + + -start_epmd false + -epmd_module epmd_static + +Lastly net_kernel has to be configured to listen to the port, this is done in the +[sys.config.src](backend/config/sys.config.src): + +``` +{kernel, [{logger,[{handler,default,logger_std_h,#{}}]}, + %%,{logger_level,info} + {inet_dist_listen_min, 12345}, + {inet_dist_listen_max, 12345} + ]}, +``` + +## Modify dockerwatch + +The modification of the dockerwatch module to use mnesia is pretty straight forward. +You can see the end result [here](dockerwatch/src/dockerwatch.erl). + +The same modifications with distribution port has to be done for dockerwatch as well. +As we want to be able to scale the number of dockerwatch frontends as load increases +we need to use a node name that is unique in the cluster. Using the IP address of +the pod is a simple solution that works for now. + +In order to get the IP the following config has to be added to the dockerwatch deployment +config: + +``` + env: + - name: IP + valueFrom: + fieldRef: + fieldPath: status.podIP +``` + +and then we just use `${IP}` in [vm.args.src](dockerwatch/config/vm.args.src): + + -name dockerwatch@${IP} + +Also mnesia is configured to connect to the backend service at startup in +[sys.config.src](dockerwatch/config/sys.config.src). + +``` +{mnesia, [{extra_db_nodes,['dockerwatch@backend.default.svc.cluster.local']}]} +``` + +## Deploy the backend + +We first setup the backend node as a service in order to easily find how to connect with it +from the dockerwatch nodes. This only works if there is only one backend node. If you +want to add more you need to use a more complex solution. + +``` +kubectl create service clusterip backend --tcp=12345:12345 +service/backend created +``` + +The backend is build like this: + + eval $(minikube docker-env) + docker build -t backend -f Dockerfile.backend . + +and then deployed like this: + +``` +cat < kubectl create service nodeport dockerwatch --tcp=8080:8080 --tcp=8443:8443 +service/dockerwatch created +> ./create-certs $(minikube ip) +...... +> kubectl create secret generic dockerwatch --from-file=ssl/ +secret/dockerwatch created +> eval $(minikube docker-env) +> docker build -t dockerwatch . +``` + +with some small modifications to the config: + +``` +cat < curl -H "Content-Type: application/json" -X POST -d "" $(minikube service dockerwatch --url | head -1)/cnt +> curl -H "Content-Type: application/json" -X POST -d "{}" $(minikube service dockerwatch --url | head -1)/cnt +> curl --cacert ssl/dockerwatch-ca.pem -H "Accept: application/json" $(minikube service dockerwatch --url --https | tail -1) +["cnt"] +> curl --cacert ssl/dockerwatch-ca.pem -H "Accept: application/json" $(minikube service dockerwatch --url --https | tail -1)/cnt +{"cnt":2} +``` diff --git a/advanced_examples/minikube-dist/backend-deploy.yaml b/advanced_examples/minikube-dist/backend-deploy.yaml new file mode 100644 index 0000000..c97ee9c --- /dev/null +++ b/advanced_examples/minikube-dist/backend-deploy.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + ## Name and labels of the Deployment + labels: + app: backend + name: backend +spec: + replicas: 1 + selector: + matchLabels: + app: backend + template: + metadata: + labels: + app: backend + spec: + containers: + ## The container to launch + - image: backend + name: backend + imagePullPolicy: Never +--- +apiVersion: v1 +kind: Service +metadata: + name: backend +spec: + ports: + - port: 12345 + protocol: TCP + selector: + app: backend \ No newline at end of file diff --git a/advanced_examples/minikube-dist/backend/config/sys.config.src b/advanced_examples/minikube-dist/backend/config/sys.config.src new file mode 100644 index 0000000..a90374d --- /dev/null +++ b/advanced_examples/minikube-dist/backend/config/sys.config.src @@ -0,0 +1,7 @@ +[%% Kernel/logger + {kernel, [{logger,[{handler,default,logger_std_h,#{}}]}, + %%,{logger_level,info} + {inet_dist_listen_min, 12345}, + {inet_dist_listen_max, 12345} + ]} +]. diff --git a/advanced_examples/minikube-dist/backend/config/vm.args.src b/advanced_examples/minikube-dist/backend/config/vm.args.src new file mode 100644 index 0000000..e04d4d4 --- /dev/null +++ b/advanced_examples/minikube-dist/backend/config/vm.args.src @@ -0,0 +1,4 @@ +-name dockerwatch@backend.default.svc.cluster.local +-setcookie dockerwatch +-start_epmd false +-epmd_module epmd_static diff --git a/advanced_examples/minikube-dist/backend/rebar.config b/advanced_examples/minikube-dist/backend/rebar.config new file mode 100644 index 0000000..735d609 --- /dev/null +++ b/advanced_examples/minikube-dist/backend/rebar.config @@ -0,0 +1,16 @@ + +{deps, []}. + +{relx, [{release, {"backend", "1.0.0"}, [backend]}, + {vm_args_src, "config/vm.args.src"}, + {sys_config_src, "config/sys.config.src"}, + {dev_mode, true}, + {include_erts, false}, + {extended_start_script, true} + ]}. + +{profiles, [{prod, [{relx, [{dev_mode, false}, + {include_erts, true}, + {include_src, false}]}]} + ]}. +%% vim: ft=erlang diff --git a/advanced_examples/minikube-dist/backend/src/backend.app.src b/advanced_examples/minikube-dist/backend/src/backend.app.src new file mode 100644 index 0000000..9187b57 --- /dev/null +++ b/advanced_examples/minikube-dist/backend/src/backend.app.src @@ -0,0 +1,14 @@ +%% Feel free to use, reuse and abuse the code in this file. + +{application, backend, [ + {description, "Mnesia node."}, + {vsn, "1.0.0"}, + {modules, []}, + {registered, [backend_sup]}, + {applications, [kernel, + stdlib, + mnesia + ]}, + {mod, {backend_app, []}}, + {env, []} +]}. diff --git a/advanced_examples/minikube-dist/backend/src/backend_app.erl b/advanced_examples/minikube-dist/backend/src/backend_app.erl new file mode 100644 index 0000000..4df0b33 --- /dev/null +++ b/advanced_examples/minikube-dist/backend/src/backend_app.erl @@ -0,0 +1,23 @@ +%% +%% Copyright (C) 2014 Björn-Egil Dahlberg +%% +%% File: dockerwatch_app.erl +%% Author: Björn-Egil Dahlberg +%% Created: 2014-09-10 +%% + +-module(backend_app). +-behaviour(application). + +-export([start/2,stop/1]). +%% API. + +start(_Type, _Args) -> + mnesia:change_table_copy_type(schema, node(), disc_copies), + mnesia:create_table(dockerwatch, + [{disc_copies,[node()]}, + {ram_copies,[]}]), + {ok, self()}. + +stop(_State) -> + ok. diff --git a/advanced_examples/minikube-dist/backend/src/epmd_static.erl b/advanced_examples/minikube-dist/backend/src/epmd_static.erl new file mode 100644 index 0000000..f6e4435 --- /dev/null +++ b/advanced_examples/minikube-dist/backend/src/epmd_static.erl @@ -0,0 +1,27 @@ +%% +%% Copyright (C) 2014 Björn-Egil Dahlberg +%% +%% File: dockerwatch_app.erl +%% Author: Björn-Egil Dahlberg +%% Created: 2014-09-10 +%% + +-module(epmd_static). + +-export([start_link/0, register_node/2, register_node/3, + port_please/2, address_please/3]). +%% API. + +start_link() -> + ignore. + +register_node(Name, Port) -> + register_node(Name, Port, inet_tcp). +register_node(_Name, _Port, _Driver) -> + {ok, 0}. + +port_please(_Name, _Host) -> + {port, 12345, 5}. + +address_please(Name, Host, AddressFamily) -> + erl_epmd:address_please(Name, Host, AddressFamily). diff --git a/advanced_examples/minikube-dist/create-certs b/advanced_examples/minikube-dist/create-certs new file mode 100755 index 0000000..69ffc4d --- /dev/null +++ b/advanced_examples/minikube-dist/create-certs @@ -0,0 +1,22 @@ +#!/bin/sh + +set -e + +if [ ! -d ssl ]; then + mkdir ssl +fi + +# Create the root CA (Certificate Authority) +openssl genrsa -out ssl/dockerwatch-ca.key 4096 + +## Certificate signing request for root CA +openssl req -x509 -new -nodes -key ssl/dockerwatch-ca.key -sha256 -days 1024 -subj "/C=SE/" -out ssl/dockerwatch-ca.pem + +# Create the server certificate +openssl genrsa -out ssl/dockerwatch-server.key 4096 + +## Certificate signing request for server certificate +openssl req -new -key ssl/dockerwatch-server.key -subj "/C=SE/CN=$1/" -out ssl/dockerwatch-server.csr + +## Sign the server certificate using the root CA +openssl x509 -req -in ssl/dockerwatch-server.csr -CA ssl/dockerwatch-ca.pem -CAkey ssl/dockerwatch-ca.key -CAcreateserial -out ssl/dockerwatch-server.pem -days 500 -sha256 diff --git a/advanced_examples/minikube-dist/dockerwatch-deploy.yaml b/advanced_examples/minikube-dist/dockerwatch-deploy.yaml new file mode 100644 index 0000000..553a8c7 --- /dev/null +++ b/advanced_examples/minikube-dist/dockerwatch-deploy.yaml @@ -0,0 +1,42 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + ## Name and labels of the Deployment + labels: + app: dockerwatch + name: dockerwatch +spec: + replicas: 10 + selector: + matchLabels: + app: dockerwatch + template: + metadata: + labels: + app: dockerwatch + spec: + containers: + ## The container to launch + - image: dockerwatch + name: dockerwatch + imagePullPolicy: Never ## Set to Never as we built the image in the cluster + stdin: true + tty: true + ports: + - containerPort: 8080 + protocol: TCP + - containerPort: 8443 + protocol: TCP + volumeMounts: + - name: kube-keypair + readOnly: true + mountPath: /etc/ssl/certs + env: + - name: IP + valueFrom: + fieldRef: + fieldPath: status.podIP + volumes: + - name: kube-keypair + secret: + secretName: dockerwatch diff --git a/advanced_examples/minikube-dist/dockerwatch/config/sys.config.src b/advanced_examples/minikube-dist/dockerwatch/config/sys.config.src new file mode 100644 index 0000000..cd79c06 --- /dev/null +++ b/advanced_examples/minikube-dist/dockerwatch/config/sys.config.src @@ -0,0 +1,8 @@ +[%% Kernel/logger + {kernel, [{logger,[{handler,default,logger_std_h,#{}}]}, + %%,{logger_level,info} + {inet_dist_listen_min, 12345}, + {inet_dist_listen_max, 12345} + ]}, + {mnesia, [{extra_db_nodes,['dockerwatch@backend.default.svc.cluster.local']}]} +]. diff --git a/advanced_examples/minikube-dist/dockerwatch/config/vm.args.src b/advanced_examples/minikube-dist/dockerwatch/config/vm.args.src new file mode 100644 index 0000000..2990ae2 --- /dev/null +++ b/advanced_examples/minikube-dist/dockerwatch/config/vm.args.src @@ -0,0 +1,4 @@ +-name dockerwatch@${IP} +-setcookie dockerwatch +-start_epmd false +-epmd_module epmd_static diff --git a/advanced_examples/minikube-dist/dockerwatch/rebar.config b/advanced_examples/minikube-dist/dockerwatch/rebar.config new file mode 100644 index 0000000..0420548 --- /dev/null +++ b/advanced_examples/minikube-dist/dockerwatch/rebar.config @@ -0,0 +1,17 @@ + +{deps, [{jsone, "1.4.7"}, %% JSON Encode/Decode + {cowboy, "2.5.0"}]}. %% HTTP Server + +{relx, [{release, {"dockerwatch", "1.0.0"}, [dockerwatch]}, + {vm_args_src, "config/vm.args.src"}, + {sys_config_src, "config/sys.config.src"}, + {dev_mode, true}, + {include_erts, false}, + {extended_start_script, true} + ]}. + +{profiles, [{prod, [{relx, [{dev_mode, false}, + {include_erts, true}, + {include_src, false}]}]} + ]}. +%% vim: ft=erlang diff --git a/advanced_examples/minikube-dist/dockerwatch/src/dockerwatch.app.src b/advanced_examples/minikube-dist/dockerwatch/src/dockerwatch.app.src new file mode 100644 index 0000000..c86cd07 --- /dev/null +++ b/advanced_examples/minikube-dist/dockerwatch/src/dockerwatch.app.src @@ -0,0 +1,17 @@ +%% Feel free to use, reuse and abuse the code in this file. + +{application, dockerwatch, [ + {description, "Cowboy REST Hello World example."}, + {vsn, "1.0.0"}, + {modules, []}, + {registered, [dockerwatch_sup]}, + {applications, [ + kernel, + stdlib, + jsone, + cowboy, + mnesia + ]}, + {mod, {dockerwatch_app, []}}, + {env, []} +]}. diff --git a/advanced_examples/minikube-dist/dockerwatch/src/dockerwatch.erl b/advanced_examples/minikube-dist/dockerwatch/src/dockerwatch.erl new file mode 100644 index 0000000..d5be3f7 --- /dev/null +++ b/advanced_examples/minikube-dist/dockerwatch/src/dockerwatch.erl @@ -0,0 +1,69 @@ +%% +%% Copyright (C) 2014 Björn-Egil Dahlberg +%% +%% File: dockerwatch.erl +%% Author: Björn-Egil Dahlberg +%% Created: 2014-09-10 +%% + +-module(dockerwatch). + +-export([start_link/0, all/0, create/1, get/1, increment/2, decrement/2]). + +-type counter() :: binary(). + +-spec start_link() -> {ok, pid()}. +start_link() -> + ok = mnesia:wait_for_tables([?MODULE], 15000), + ignore. + +-spec all() -> [counter()]. +all() -> + case mnesia:transaction( + fun() -> + mnesia:select(?MODULE, [{{'$1','_'},[],['$1']}]) + end) of + {atomic, Res} -> + Res + end. + +-spec create(counter()) -> ok | already_exists. +create(CounterName) -> + case mnesia:transaction( + fun() -> + case mnesia:read(?MODULE, CounterName) of + [] -> + mnesia:write({?MODULE,CounterName,0}), + ok; + _Else -> + already_exists + end + end) of + {atomic, Res} -> + Res + end. + +-spec get(counter()) -> integer(). +get(CounterName) -> + case mnesia:transaction( + fun() -> + mnesia:read(?MODULE, CounterName) + end) of + {atomic,[{?MODULE, _, Cnt}]} -> + Cnt + end. + +-spec increment(counter(), integer()) -> ok. +increment(CounterName, Howmuch) -> + case mnesia:transaction( + fun() -> + [{?MODULE, _, Cnt}] = mnesia:read(?MODULE, CounterName), + mnesia:write({?MODULE, CounterName, Cnt + Howmuch}) + end) of + {atomic, ok} -> + ok + end. + +-spec decrement(counter(), integer()) -> ok. +decrement(CounterName, Howmuch) -> + increment(CounterName, -1 * Howmuch). diff --git a/advanced_examples/minikube-dist/dockerwatch/src/dockerwatch_app.erl b/advanced_examples/minikube-dist/dockerwatch/src/dockerwatch_app.erl new file mode 100644 index 0000000..a85b5b6 --- /dev/null +++ b/advanced_examples/minikube-dist/dockerwatch/src/dockerwatch_app.erl @@ -0,0 +1,19 @@ +%% +%% Copyright (C) 2014 Björn-Egil Dahlberg +%% +%% File: dockerwatch_app.erl +%% Author: Björn-Egil Dahlberg +%% Created: 2014-09-10 +%% + +-module(dockerwatch_app). +-behaviour(application). + +-export([start/2,stop/1]). +%% API. + +start(_Type, _Args) -> + dockerwatch_sup:start_link(). + +stop(_State) -> + ok. diff --git a/advanced_examples/minikube-dist/dockerwatch/src/dockerwatch_handler.erl b/advanced_examples/minikube-dist/dockerwatch/src/dockerwatch_handler.erl new file mode 100644 index 0000000..1ac15f9 --- /dev/null +++ b/advanced_examples/minikube-dist/dockerwatch/src/dockerwatch_handler.erl @@ -0,0 +1,103 @@ +%% +%% Copyright (C) 2014 Björn-Egil Dahlberg +%% +%% File: dockerwatch_handler.erl +%% Author: Björn-Egil Dahlberg +%% Created: 2014-09-10 +%% + +-module(dockerwatch_handler). + +-export([init/2]). +-export([allowed_methods/2]). +-export([content_types_accepted/2]). +-export([content_types_provided/2]). +-export([handle_post/2]). +-export([to_html/2]). +-export([to_json/2]). +-export([to_text/2]). + +init(Req, []) -> + {cowboy_rest, Req, []}. + +%% Which HTTP methods are allowed +allowed_methods(Req, State) -> + {[<<"GET">>, <<"POST">>], Req, State}. + +%% Which content types are accepted by POST/PUT requests +content_types_accepted(Req, State) -> + {[{{<<"application">>, <<"json">>, []}, handle_post}], + Req, State}. + +%% Handle the POST/PUT request +handle_post(Req, State) -> + case cowboy_req:binding(counter_name, Req) of + undefined -> + {false, Req, State}; + Name -> + case cowboy_req:has_body(Req) of + true -> + {ok, Body, Req3} = cowboy_req:read_body(Req), + Json = jsone:decode(Body), + ActionBin = maps:get(<<"action">>, Json, <<"increment">>), + Value = maps:get(<<"value">>, Json, 1), + Action = list_to_atom(binary_to_list(ActionBin)), + ok = dockerwatch:Action(Name, Value), + {true, Req3, State}; + false -> + ok = dockerwatch:create(Name), + {true, Req, State} + end + end. + +%% Which content types we handle for GET/HEAD requests +content_types_provided(Req, State) -> + {[{<<"text/html">>, to_html}, + {<<"application/json">>, to_json}, + {<<"text/plain">>, to_text} + ], Req, State}. + + +%% Return counters/counter as json +to_json(Req, State) -> + Resp = case cowboy_req:binding(counter_name, Req) of + undefined -> + dockerwatch:all(); + Counter -> + #{ Counter => dockerwatch:get(Counter) } + end, + {jsone:encode(Resp), Req, State}. + +%% Return counters/counter as plain text +to_text(Req, State) -> + Resp = case cowboy_req:binding(counter_name, Req) of + undefined -> + [io_lib:format("~s~n",[Counter]) || Counter <- dockerwatch:all()]; + Counter -> + io_lib:format("~p",[dockerwatch:get(Counter)]) + end, + {Resp, Req, State}. + +%% Return counters/counter as html +to_html(Req, State) -> + Body = case cowboy_req:binding(counter_name, Req) of + undefined -> + Counters = dockerwatch:all(), + ["\n"]; + Counter -> + Value = dockerwatch:get(Counter), + io_lib:format("~s = ~p",[Counter, Value]) + end, + {[html_head(),Body,html_tail()], Req, State}. + +html_head() -> + <<" + + + dockerwatch + ">>. +html_tail() -> + <<" + ">>. diff --git a/advanced_examples/minikube-dist/dockerwatch/src/dockerwatch_sup.erl b/advanced_examples/minikube-dist/dockerwatch/src/dockerwatch_sup.erl new file mode 100644 index 0000000..ef298ad --- /dev/null +++ b/advanced_examples/minikube-dist/dockerwatch/src/dockerwatch_sup.erl @@ -0,0 +1,49 @@ +%% +%% Copyright (C) 2014 Björn-Egil Dahlberg +%% +%% File: dockerwatch_sup.erl +%% Author: Björn-Egil Dahlberg +%% Created: 2014-09-10 +%% + +-module(dockerwatch_sup). +-behaviour(supervisor). + +-export([start_link/0,init/1]). + +%% API. + +-spec start_link() -> {ok, pid()}. +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%% supervisor. + +init([]) -> + CertsDir = "/etc/ssl/certs/", + + Dispatch = cowboy_router:compile([ + {'_', [{"/[:counter_name]", dockerwatch_handler, []}]} + ]), + + HTTPS = ranch:child_spec( + cowboy_https, 100, ranch_ssl, + [{port, 8443}, + {cacertfile, filename:join(CertsDir, "dockerwatch-ca.pem")}, + {certfile, filename:join(CertsDir, "dockerwatch-server.pem")}, + {keyfile, filename:join(CertsDir, "dockerwatch-server.key")}], + cowboy_tls, + #{env=>#{dispatch=>Dispatch}}), + + HTTP = ranch:child_spec( + cowboy_http, 100, ranch_tcp, + [{port, 8080}], + cowboy_clear, + #{env=>#{dispatch=>Dispatch}}), + + Counter = {dockerwatch, {dockerwatch, start_link, []}, + permanent, 5000, worker, [dockerwatch]}, + + Procs = [Counter, HTTP, HTTPS], + + {ok, {{one_for_one, 10, 10}, Procs}}. diff --git a/advanced_examples/minikube-dist/dockerwatch/src/epmd_static.erl b/advanced_examples/minikube-dist/dockerwatch/src/epmd_static.erl new file mode 100644 index 0000000..f6e4435 --- /dev/null +++ b/advanced_examples/minikube-dist/dockerwatch/src/epmd_static.erl @@ -0,0 +1,27 @@ +%% +%% Copyright (C) 2014 Björn-Egil Dahlberg +%% +%% File: dockerwatch_app.erl +%% Author: Björn-Egil Dahlberg +%% Created: 2014-09-10 +%% + +-module(epmd_static). + +-export([start_link/0, register_node/2, register_node/3, + port_please/2, address_please/3]). +%% API. + +start_link() -> + ignore. + +register_node(Name, Port) -> + register_node(Name, Port, inet_tcp). +register_node(_Name, _Port, _Driver) -> + {ok, 0}. + +port_please(_Name, _Host) -> + {port, 12345, 5}. + +address_please(Name, Host, AddressFamily) -> + erl_epmd:address_please(Name, Host, AddressFamily).