Continuing this short series of writing a simple echo service web API along with the docker and k8s requirements, we’re now going to turn our attention to a Elixir implementation.
Implementation
I’m going to be using Visual Code and dev containers, so I created a folder echo_service which has a folder named .devcontainer with the following devcontainer.json
{
"image": "elixir",
"forwardPorts": [3000]
}
Next I opened Visual Code in the echo_service folder and it should detect the devtonainer and ask if you want to reopen in the devcontainer. To which we do.
I’m going to use Phoenix Server (phx), so I open the terminal in Visual Code and run the following
-
I needed to install phx installer, using
mix archive.install hex phx_new
-
Next I want to generate a minimal phx server, hence run the following
mix phx.new echo_service --no-html --no-ecto --no-mailer --no-dashboard --no-assets --no-gettext
When this prompt appears, type y
Fetch and install dependencies? [Yn]
- Now cd into the newly created echo_service folder
- To check everything is working, run
mix phx.server
Next we need to add a couple of controllers (well we could just use one but I’m going to create a Echo controller and a Health controller). So in lib/echo_service_web/controllers add the files echo_controller.ex and health_controller.ex
The echo_controller.ex looks like this
defmodule EchoServiceWeb.EchoController do
use Phoenix.Controller, formats: [:html, :json]
def index(conn, %{"text" => text}) do
send_resp(conn, 200, "Elixir Echo: #{text}")
end
end
The health_controller.exe should look like this
defmodule EchoServiceWeb.HealthController do
use Phoenix.Controller, formats: [:html, :json]
def livez(conn, _params) do
send_resp(conn, 200, "Live")
end
def readyz(conn, _params) do
send_resp(conn, 200, "Ready")
end
end
In the parent folder (i.e. lib/echo_service_web) edit the router.exe so it looks like this
defmodule EchoServiceWeb.Router do
use EchoServiceWeb, :router
pipeline :api do
plug :accepts, ["json"]
end
scope "/", EchoServiceWeb do
# pipe_through :api
get "/echo", EchoController, :index
get "/livez", HealthController, :livez
get "/readyz", HealthController, :readyz
end
end
Now we can run mix phx.server again (ctrl+c twice to shut any existing running instance).
Dockerfile
Next up we need to create our Dockerfile
FROM elixir:latest RUN mkdir /app COPY . /app WORKDIR /app RUN mix local.hex --force RUN mix do compile ENV PORT=8080 EXPOSE 8080 CMD ["mix", "phx.server"]
Note: In Linux port 80 might be locked down, hence we use port 8080 – to override the default port in phx we also set the environment variable PORT.
To build this, run
docker build -t putridparrot.echo_service:v1 .
Don’t forget to change the name to your preferred name.
To test this, run
docker run -p 8080:8080 putridparrot.echo_service:v1
Kubernetes
If all wen well we’ve not tested our application and see it working from a docker image, so now we need to create the deployment etc. for Kubernete’s. Let’s assume you’ve pushed you image to Docker or another container registry such as Azure – I’m call my container registry putridparrotreg.
I’m also not going to use helm at this point as I just want a (relatively) simple yaml file to run from kubectl, so create a deployment.yaml file, we’ll store all the configurations, deployment, service and ingress in this one file jus for simplicity.
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo
namespace: dev
labels:
app: echo
spec:
replicas: 1
selector:
matchLabels:
app: echo
template:
metadata:
labels:
app: echo
spec:
containers:
- name: echo
image: putridparrotreg/putridparrot.echo_service:v1
ports:
- containerPort: 8080
resources:
requests:
memory: "100Mi"
cpu: "100m"
limits:
memory: "200Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /livez
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: echo_service
namespace: dev
labels:
app: echo
spec:
type: ClusterIP
selector:
app: echo
ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: echo-ingress
namespace: dev
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: mydomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: echo_service
port:
number: 80
Don’t forget to change the “host” and image to suit, also this assume you created a namespace “dev” for your app. See Creating a local container registry for information on setting up your own container registry.