You've already forked docker-erlang-example
mirror of
https://github.com/erlang/docker-erlang-example.git
synced 2025-08-08 17:42:05 +03:00
Refactoring: Copy minikube example
This commit copies Lukas Larsson's (@garazdawi) and Siri Hansen's (@sirihansen) example from: https://github.com/erlang/docker-erlang-example/tree/minikube-dist
This commit is contained in:
2
advanced_examples/minikube-dist/.gitignore
vendored
Normal file
2
advanced_examples/minikube-dist/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
_build/
|
||||||
|
rebar.lock
|
51
advanced_examples/minikube-dist/.travis.yml
Normal file
51
advanced_examples/minikube-dist/.travis.yml
Normal file
@@ -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"
|
26
advanced_examples/minikube-dist/Docker-Cheat-Sheet.md
Normal file
26
advanced_examples/minikube-dist/Docker-Cheat-Sheet.md
Normal file
@@ -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
|
37
advanced_examples/minikube-dist/Dockerfile
Normal file
37
advanced_examples/minikube-dist/Dockerfile
Normal file
@@ -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"]
|
37
advanced_examples/minikube-dist/Dockerfile.backend
Normal file
37
advanced_examples/minikube-dist/Dockerfile.backend
Normal file
@@ -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"]
|
23
advanced_examples/minikube-dist/README-CERTS.md
Normal file
23
advanced_examples/minikube-dist/README-CERTS.md
Normal file
@@ -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
|
264
advanced_examples/minikube-dist/README.md
Normal file
264
advanced_examples/minikube-dist/README.md
Normal file
@@ -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 <<EOF | kubectl apply -f -
|
||||||
|
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
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Deploy Dockerwatch
|
||||||
|
|
||||||
|
Dockerwatch is deployed as previously:
|
||||||
|
|
||||||
|
```
|
||||||
|
> 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 <<EOF | kubectl apply -f -
|
||||||
|
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
|
||||||
|
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
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
We can then test the API using the same curl commands as the [simple demo](http://github.com/erlang/docker-erlang-example/tree/minikube-simple):
|
||||||
|
|
||||||
|
```
|
||||||
|
> 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}
|
||||||
|
```
|
33
advanced_examples/minikube-dist/backend-deploy.yaml
Normal file
33
advanced_examples/minikube-dist/backend-deploy.yaml
Normal file
@@ -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
|
@@ -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}
|
||||||
|
]}
|
||||||
|
].
|
@@ -0,0 +1,4 @@
|
|||||||
|
-name dockerwatch@backend.default.svc.cluster.local
|
||||||
|
-setcookie dockerwatch
|
||||||
|
-start_epmd false
|
||||||
|
-epmd_module epmd_static
|
16
advanced_examples/minikube-dist/backend/rebar.config
Normal file
16
advanced_examples/minikube-dist/backend/rebar.config
Normal file
@@ -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
|
14
advanced_examples/minikube-dist/backend/src/backend.app.src
Normal file
14
advanced_examples/minikube-dist/backend/src/backend.app.src
Normal file
@@ -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, []}
|
||||||
|
]}.
|
23
advanced_examples/minikube-dist/backend/src/backend_app.erl
Normal file
23
advanced_examples/minikube-dist/backend/src/backend_app.erl
Normal file
@@ -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.
|
27
advanced_examples/minikube-dist/backend/src/epmd_static.erl
Normal file
27
advanced_examples/minikube-dist/backend/src/epmd_static.erl
Normal file
@@ -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).
|
22
advanced_examples/minikube-dist/create-certs
Executable file
22
advanced_examples/minikube-dist/create-certs
Executable file
@@ -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
|
42
advanced_examples/minikube-dist/dockerwatch-deploy.yaml
Normal file
42
advanced_examples/minikube-dist/dockerwatch-deploy.yaml
Normal file
@@ -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
|
@@ -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']}]}
|
||||||
|
].
|
@@ -0,0 +1,4 @@
|
|||||||
|
-name dockerwatch@${IP}
|
||||||
|
-setcookie dockerwatch
|
||||||
|
-start_epmd false
|
||||||
|
-epmd_module epmd_static
|
17
advanced_examples/minikube-dist/dockerwatch/rebar.config
Normal file
17
advanced_examples/minikube-dist/dockerwatch/rebar.config
Normal file
@@ -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
|
@@ -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, []}
|
||||||
|
]}.
|
@@ -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).
|
@@ -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.
|
@@ -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(),
|
||||||
|
["<ul>\n",
|
||||||
|
[io_lib:format("<li>~s</li>\n", [Counter]) || Counter <- Counters],
|
||||||
|
"</ul>\n"];
|
||||||
|
Counter ->
|
||||||
|
Value = dockerwatch:get(Counter),
|
||||||
|
io_lib:format("~s = ~p",[Counter, Value])
|
||||||
|
end,
|
||||||
|
{[html_head(),Body,html_tail()], Req, State}.
|
||||||
|
|
||||||
|
html_head() ->
|
||||||
|
<<"<html>
|
||||||
|
<head>
|
||||||
|
<meta charset=\"utf-8\">
|
||||||
|
<title>dockerwatch</title>
|
||||||
|
</head>">>.
|
||||||
|
html_tail() ->
|
||||||
|
<<"</body>
|
||||||
|
</html>">>.
|
@@ -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}}.
|
@@ -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).
|
Reference in New Issue
Block a user