From 2746eadab7442db77390f03350a31dea10ad826c Mon Sep 17 00:00:00 2001 From: Alex Mickelson Date: Tue, 3 Mar 2026 15:30:25 -0700 Subject: [PATCH] testing build and test --- .gitea/workflow/pipeline.yml | 49 ++++++++++++++++ backend/.dockerignore | 71 +++++++++++++---------- backend/Dockerfile | 63 +++++++++++++------- backend/dev.Dockerfile | 2 +- docker-compose.prod.yml | 109 ----------------------------------- kubernetes/0-namespace.yml | 4 ++ kubernetes/frontend.yml | 62 ++++++++++++++++++++ kubernetes/node-1.yml | 61 ++++++++++++++++++++ kubernetes/node-2.yml | 61 ++++++++++++++++++++ kubernetes/node-3.yml | 61 ++++++++++++++++++++ otel-collector-config.yaml | 18 ++---- 11 files changed, 383 insertions(+), 178 deletions(-) create mode 100644 .gitea/workflow/pipeline.yml delete mode 100644 docker-compose.prod.yml create mode 100644 kubernetes/0-namespace.yml create mode 100644 kubernetes/frontend.yml create mode 100644 kubernetes/node-1.yml create mode 100644 kubernetes/node-2.yml create mode 100644 kubernetes/node-3.yml diff --git a/.gitea/workflow/pipeline.yml b/.gitea/workflow/pipeline.yml new file mode 100644 index 0000000..8546631 --- /dev/null +++ b/.gitea/workflow/pipeline.yml @@ -0,0 +1,49 @@ +name: CI/CD Pipeline + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + +jobs: + build: + runs-on: home-server + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: test + run: | + cd backend + nix-shell -p elixir elixir_ls --run ' + mix do deps.get, test + ' + + - name: Build and push backend image + run: | + cd client + docker build -t alexmickelson/elixir-demo-frontend:$GITHUB_BUILD_NUMBER . + docker push alexmickelson/elixir-demo-frontend:$GITHUB_BUILD_NUMBER + cd ../backend + docker build -t alexmickelson/elixir-demo-backend:$GITHUB_BUILD_NUMBER . + docker push alexmickelson/elixir-demo-backend:$GITHUB_BUILD_NUMBER + + # - name: Deploy to Kubernetes + # run: | + # kubectl apply -f kubernetes/namespace.yml + # kubectl get secret backend-secret --namespace alex-elixir-demo || \ + # kubectl create secret generic backend-secret \ + # --namespace alex-elixir-demo \ + # --from-literal=cookie=$(openssl rand -hex 32) + + # for file in kubernetes/*.yml; do + # cat "$file" | envsubst | kubectl apply -f - + # done diff --git a/backend/.dockerignore b/backend/.dockerignore index 6090094..5d2adff 100644 --- a/backend/.dockerignore +++ b/backend/.dockerignore @@ -1,39 +1,46 @@ -# Git +# This file excludes paths from the Docker build context. +# +# By default, Docker's build context includes all files (and folders) in the +# current directory. Even if a file isn't copied into the container it is still sent to +# the Docker daemon. +# +# There are multiple reasons to exclude files from the build context: +# +# 1. Prevent nested folders from being copied into the container (ex: exclude +# /assets/node_modules when copying /assets) +# 2. Reduce the size of the build context and improve build time (ex. /build, /deps, /doc) +# 3. Avoid sending files containing sensitive information +# +# More information on using .dockerignore is available here: +# https://docs.docker.com/engine/reference/builder/#dockerignore-file + +.dockerignore + +# Ignore git, but keep git HEAD and refs to access current commit hash if needed: +# +# $ cat .git/HEAD | awk '{print ".git/"$2}' | xargs cat +# d0b8727759e1e0e7aa3d41707d12376e373d5ecc .git -.gitignore +!.git/HEAD +!.git/refs + +# Common development/test artifacts +/cover/ +/doc/ +/test/ +/tmp/ +.elixir_ls # Mix artifacts -/_build -/deps +/_build/ +/deps/ *.ez -# Build artifacts +# Generated on crash by the VM erl_crash.dump -# Static artifacts -/priv/static/ - -# Environment files -.env -.env.local - -# Editor files -.vscode -.idea -*.swp -*.swo -*~ - -# OS files -.DS_Store -Thumbs.db - -# Test files -/cover -/test - -# Documentation -/doc - -# Node modules (if any frontend code was in here) -node_modules +# Static artifacts - These should be fetched and built inside the Docker image +# https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Release.html#module-docker +/assets/node_modules/ +/priv/static/assets/ +/priv/static/cache_manifest.json diff --git a/backend/Dockerfile b/backend/Dockerfile index 883e897..b69b37a 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,39 +1,58 @@ -FROM hexpm/elixir:1.15.7-erlang-26.1.2-alpine-3.18.4 AS build +ARG ELIXIR_VERSION=1.18.4 +ARG OTP_VERSION=27.3.4.8 +ARG DEBIAN_VERSION=trixie-20260223-slim + +ARG BUILDER_IMAGE="docker.io/hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" +ARG RUNNER_IMAGE="docker.io/debian:${DEBIAN_VERSION}" + +FROM ${BUILDER_IMAGE} AS builder + +RUN apt-get update \ + && apt-get install -y --no-install-recommends build-essential git \ + && rm -rf /var/lib/apt/lists/* -RUN apk add --no-cache build-base git WORKDIR /app -RUN mix local.hex --force && \ - mix local.rebar --force -ENV MIX_ENV=prod + +RUN mix local.hex --force \ + && mix local.rebar --force + +ENV MIX_ENV="prod" + COPY mix.exs mix.lock ./ RUN mix deps.get --only $MIX_ENV RUN mkdir config - COPY config/config.exs config/${MIX_ENV}.exs config/ RUN mix deps.compile -COPY lib lib -RUN mix compile -COPY config/runtime.exs config/ COPY priv priv +COPY lib lib + +RUN mix compile + +COPY config/runtime.exs config/ RUN mix release -FROM alpine:3.18.4 AS app +FROM ${RUNNER_IMAGE} AS final -RUN apk add --no-cache libstdc++ openssl ncurses-libs bash -ENV USER="elixir" -WORKDIR /app -RUN addgroup -g 1000 $USER && \ - adduser -D -u 1000 -G $USER $USER +RUN apt-get update \ + && apt-get install -y --no-install-recommends libstdc++6 openssl libncurses6 locales ca-certificates \ + && rm -rf /var/lib/apt/lists/* -COPY --from=build --chown=elixir:elixir /app/_build/prod/rel/backend ./ +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen \ + && locale-gen -USER elixir +ENV LANG=en_US.UTF-8 +ENV LANGUAGE=en_US:en +ENV LC_ALL=en_US.UTF-8 + +WORKDIR "/app" +RUN chown nobody /app + +ENV MIX_ENV="prod" + +COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/backend ./ + +USER nobody -ENV HOME=/app -ENV MIX_ENV=prod ENV PHX_SERVER=true - -EXPOSE 4000 4369 9000-9100 - CMD ["/app/bin/backend", "start"] diff --git a/backend/dev.Dockerfile b/backend/dev.Dockerfile index 32dd005..df579b6 100644 --- a/backend/dev.Dockerfile +++ b/backend/dev.Dockerfile @@ -1,4 +1,4 @@ -FROM hexpm/elixir:1.15.7-erlang-26.1.2-alpine-3.18.4 +FROM hexpm/elixir:1.18.0-rc.0-erlang-26.2.5.8-alpine-3.23.3 RUN apk add --no-cache build-base git bash wget WORKDIR /app diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml deleted file mode 100644 index 2b1a5b4..0000000 --- a/docker-compose.prod.yml +++ /dev/null @@ -1,109 +0,0 @@ -services: - phoenix1: - build: - context: ./backend - dockerfile: Dockerfile - container_name: phoenix1 - hostname: phoenix1 - environment: - - RELEASE_NODE=backend@phoenix1 - - RELEASE_DISTRIBUTION=sname - - RELEASE_COOKIE=super_secret_cookie_change_in_production - - PHX_HOST=localhost - - PHX_SERVER=true - - PORT=4000 - - DATABASE_URL=ecto://postgres:postgres@db/backend_dev - - SECRET_KEY_BASE=W8nGKNhNR8vKj6A4VnwN5h5h7RZvkKmZPqxqzLzYxXGQqC6HnKp2Wm8MNqKpQxZv - - CLUSTER_NODES=backend@phoenix1,backend@phoenix2,backend@phoenix3 - - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 - ports: - - "4001:4000" - healthcheck: - test: ["CMD", "wget", "-qO-", "http://localhost:4000/api/health"] - interval: 10s - timeout: 5s - retries: 5 - - phoenix2: - build: - context: ./backend - dockerfile: Dockerfile - container_name: phoenix2 - hostname: phoenix2 - environment: - - RELEASE_NODE=backend@phoenix2 - - RELEASE_DISTRIBUTION=sname - - RELEASE_COOKIE=super_secret_cookie_change_in_production - - PHX_HOST=localhost - - PHX_SERVER=true - - PORT=4000 - - DATABASE_URL=ecto://postgres:postgres@db/backend_dev - - SECRET_KEY_BASE=W8nGKNhNR8vKj6A4VnwN5h5h7RZvkKmZPqxqzLzYxXGQqC6HnKp2Wm8MNqKpQxZv - - CLUSTER_NODES=backend@phoenix1,backend@phoenix2,backend@phoenix3 - - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 - ports: - - "4002:4000" - healthcheck: - test: ["CMD", "wget", "-qO-", "http://localhost:4000/api/health"] - interval: 10s - timeout: 5s - retries: 5 - - phoenix3: - build: - context: ./backend - dockerfile: Dockerfile - container_name: phoenix3 - hostname: phoenix3 - environment: - - RELEASE_NODE=backend@phoenix3 - - RELEASE_DISTRIBUTION=sname - - RELEASE_COOKIE=super_secret_cookie_change_in_production - - PHX_HOST=localhost - - PHX_SERVER=true - - PORT=4000 - - DATABASE_URL=ecto://postgres:postgres@db/backend_dev - - SECRET_KEY_BASE=W8nGKNhNR8vKj6A4VnwN5h5h7RZvkKmZPqxqzLzYxXGQqC6HnKp2Wm8MNqKpQxZv - - CLUSTER_NODES=backend@phoenix1,backend@phoenix2,backend@phoenix3 - - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 - ports: - - "4003:4000" - healthcheck: - test: ["CMD", "wget", "-qO-", "http://localhost:4000/api/health"] - interval: 10s - timeout: 5s - retries: 5 - - nginx-lb: - image: nginx:alpine - container_name: nginx-lb - volumes: - - ./nginx-lb.conf:/etc/nginx/conf.d/default.conf - ports: - - "4000:80" - depends_on: - - phoenix1 - - phoenix2 - - phoenix3 - - client: - build: - context: ./client - dockerfile: Dockerfile - container_name: client - ports: - - "5173:80" - depends_on: - - nginx-lb - - otel-collector: - image: otel/opentelemetry-collector-contrib:latest - container_name: otel-collector - command: ["--config=/etc/otel-collector-config.yaml"] - volumes: - - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml - ports: - - "4318:4318" # OTLP HTTP receiver - - "4317:4317" # OTLP gRPC receiver - - "8888:8888" # Prometheus metrics - - "8889:8889" # Prometheus exporter metrics diff --git a/kubernetes/0-namespace.yml b/kubernetes/0-namespace.yml new file mode 100644 index 0000000..32e6f42 --- /dev/null +++ b/kubernetes/0-namespace.yml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: alex-elixir-demo \ No newline at end of file diff --git a/kubernetes/frontend.yml b/kubernetes/frontend.yml new file mode 100644 index 0000000..8a3f6e2 --- /dev/null +++ b/kubernetes/frontend.yml @@ -0,0 +1,62 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend-deployment + namespace: alex-elixir-demo + labels: + app: frontend +spec: + replicas: 2 + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + spec: + containers: + - name: frontend + image: alexmickelson/elixir-demo-frontend:$GITHUB_BUILD_NUMBER + ports: + - containerPort: 80 + resources: + requests: + memory: 128Mi + cpu: 100m + limits: + memory: 256Mi + cpu: 200m +--- +apiVersion: v1 +kind: Service +metadata: + namespace: alex-elixir-demo + name: frontend +spec: + selector: + app: frontend + ports: + - protocol: TCP + port: 80 + targetPort: 80 + type: ClusterIP +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + namespace: alex-elixir-demo + name: frontend +spec: + ingressClassName: nginx + rules: + - host: elixir-demo.alex-kube.duckdns.org + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: frontend + port: + number: 80 diff --git a/kubernetes/node-1.yml b/kubernetes/node-1.yml new file mode 100644 index 0000000..03fde4a --- /dev/null +++ b/kubernetes/node-1.yml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: node-1-deployment + namespace: alex-elixir-demo + labels: + app: node-1 +spec: + replicas: 1 + selector: + matchLabels: + app: node-1 + template: + metadata: + labels: + app: node-1 + spec: + containers: + - name: node-1 + image: alexmickelson/elixir-demo-backend:$GITHUB_BUILD_NUMBER + ports: + - containerPort: 4000 + env: + - name: PORT + value: "4000" + - name: NODE_NAME + value: "backend@node-1" + - name: COOKIE + valueFrom: + secretKeyRef: + name: backend-secret + key: cookie + - name: CLUSTER_NODES + value: "backend@node-1,backend@node-2,backend@node-3" + resources: + requests: + memory: 256Mi + cpu: 100m + limits: + memory: 512Mi + cpu: 500m + livenessProbe: + httpGet: + path: /api/health + port: 4000 + initialDelaySeconds: 15 + periodSeconds: 10 +--- +apiVersion: v1 +kind: Service +metadata: + namespace: alex-elixir-demo + name: node-1 +spec: + selector: + app: node-1 + ports: + - protocol: TCP + port: 4000 + targetPort: 4000 + type: ClusterIP diff --git a/kubernetes/node-2.yml b/kubernetes/node-2.yml new file mode 100644 index 0000000..661fd5c --- /dev/null +++ b/kubernetes/node-2.yml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: node-2-deployment + namespace: alex-elixir-demo + labels: + app: node-2 +spec: + replicas: 1 + selector: + matchLabels: + app: node-2 + template: + metadata: + labels: + app: node-2 + spec: + containers: + - name: node-2 + image: alexmickelson/elixir-demo-backend:$GITHUB_BUILD_NUMBER + ports: + - containerPort: 4000 + env: + - name: PORT + value: "4000" + - name: NODE_NAME + value: "backend@node-2" + - name: COOKIE + valueFrom: + secretKeyRef: + name: backend-secret + key: cookie + - name: CLUSTER_NODES + value: "backend@node-1,backend@node-2,backend@node-3" + resources: + requests: + memory: 256Mi + cpu: 100m + limits: + memory: 512Mi + cpu: 500m + livenessProbe: + httpGet: + path: /api/health + port: 4000 + initialDelaySeconds: 15 + periodSeconds: 10 +--- +apiVersion: v1 +kind: Service +metadata: + namespace: alex-elixir-demo + name: node-2 +spec: + selector: + app: node-2 + ports: + - protocol: TCP + port: 4000 + targetPort: 4000 + type: ClusterIP diff --git a/kubernetes/node-3.yml b/kubernetes/node-3.yml new file mode 100644 index 0000000..f400610 --- /dev/null +++ b/kubernetes/node-3.yml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: node-3-deployment + namespace: alex-elixir-demo + labels: + app: node-3 +spec: + replicas: 1 + selector: + matchLabels: + app: node-3 + template: + metadata: + labels: + app: node-3 + spec: + containers: + - name: node-3 + image: alexmickelson/elixir-demo-backend:$GITHUB_BUILD_NUMBER + ports: + - containerPort: 4000 + env: + - name: PORT + value: "4000" + - name: NODE_NAME + value: "backend@node-3" + - name: COOKIE + valueFrom: + secretKeyRef: + name: backend-secret + key: cookie + - name: CLUSTER_NODES + value: "backend@node-1,backend@node-2,backend@node-3" + resources: + requests: + memory: 256Mi + cpu: 100m + limits: + memory: 512Mi + cpu: 500m + livenessProbe: + httpGet: + path: /api/health + port: 4000 + initialDelaySeconds: 15 + periodSeconds: 10 +--- +apiVersion: v1 +kind: Service +metadata: + namespace: alex-elixir-demo + name: node-3 +spec: + selector: + app: node-3 + ports: + - protocol: TCP + port: 4000 + targetPort: 4000 + type: ClusterIP diff --git a/otel-collector-config.yaml b/otel-collector-config.yaml index 7defe32..1bb5cef 100644 --- a/otel-collector-config.yaml +++ b/otel-collector-config.yaml @@ -12,34 +12,24 @@ processors: send_batch_size: 1024 exporters: - # Log to console for debugging - logging: - loglevel: info - - # Export to Prometheus + debug: prometheus: endpoint: 0.0.0.0:8889 namespace: websocket_game - # Uncomment to export to Jaeger for traces - # jaeger: - # endpoint: jaeger:14250 - # tls: - # insecure: true - service: pipelines: traces: receivers: [otlp] processors: [batch] - exporters: [logging] + exporters: [debug] metrics: receivers: [otlp] processors: [batch] - exporters: [logging, prometheus] + exporters: [debug, prometheus] logs: receivers: [otlp] processors: [batch] - exporters: [logging] + exporters: [debug]