Skip to content

Commit

Permalink
Setup client deployment
Browse files Browse the repository at this point in the history
  • Loading branch information
florimondmanca committed Feb 1, 2022
1 parent 8b0d2bf commit 98511a2
Show file tree
Hide file tree
Showing 18 changed files with 188 additions and 19 deletions.
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ install-server: #- Install server dependencies
install-client: #- Install client dependencies
cd client && npm ci

build: #- Build production assets
cd client && npm run build

serve: #- Serve both the server and the client in parallel
make -j 2 serve-server serve-client

Expand All @@ -26,6 +29,12 @@ serve-server: #- Run API server
serve-client: #- Run the client
./tools/colorize_prefix.sh [client] 33 "cd client && npm run dev"

serve-prod: #- Serve both the server and the production client in parallel
make -j 2 serve-server serve-prod-client

serve-prod-client: #- Run the productionclient
./tools/colorize_prefix.sh [client] 33 "cd client && npm start"

migrate: #- Apply pending migrations
${bin}alembic upgrade head

Expand Down
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"build": "svelte-kit build",
"package": "svelte-kit package",
"preview": "svelte-kit preview",
"start": "node ./build",
"check": "svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
Expand Down
17 changes: 17 additions & 0 deletions client/src/lib/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { browser } from "$app/env";

export const getApiUrl = () => {
if (!browser) {
// During SSR, request the local API server directly.
// This assumes the same port is used in production
// and during development.
return "http://localhost:3579";
}
// In the browser, request /api on the current domain.
// This works in production because this will use
// https://<domain>/api/..., which is the public URL to the
// API server.
// This works in development because Vite is configured
// to proxy localhost:3000/api/... to the local API server.
return "/api";
}
4 changes: 3 additions & 1 deletion client/src/routes/index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
<script lang="ts">
import { createForm } from "svelte-forms-lib";
import * as yup from "yup";
import { getApiUrl } from "$lib/fetch";
const postData = async (values) => {
const data = JSON.stringify(values);
const response = await fetch("http://127.0.0.1:3579/datasets/", {
const url = `${getApiUrl()}/datasets/`;
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: data,
Expand Down
18 changes: 18 additions & 0 deletions client/svelte.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import adapter from "@sveltejs/adapter-node";
import preprocess from "svelte-preprocess";

const isDev = process.env.NODE_ENV === "development";

/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://github.com/sveltejs/svelte-preprocess
Expand All @@ -17,6 +19,22 @@ const config = {
methodOverride: {
allowed: ["PATCH", "DELETE"],
},

vite: {
...(
isDev ? {
server: {
proxy: {
// Make the location of the API server in production (<domain>/api) available during development.
'/api': {
target: 'http://localhost:3579',
rewrite: path => path.replace(/^\/api/, ""), // "/api/..." -> "/..."
}
}
}
} : {}
)
}
},
};

Expand Down
40 changes: 27 additions & 13 deletions docs/fr/ops.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,22 @@ Le déploiement et la gestion des serveurs distants est gérée à l'aide de [An
L'architecture du service déployé est la suivante :

```
┌----------------------------------------┐
WWW <--(tcp/80)--> nginx <--(tcp/3579)--> gunicorn |
└---------------------------------^------┘
|
┌ - - v - - -┐
PostgreSQL
└ - - - - - -┘
┌---------------------------------┐
WWW ------- nginx (:80) ---- node (:3000) |
| | | |
| └-------- gunicorn (:3579) |
└----------------------|----------┘
|
┌ - - ┴ - - -┐
PostgreSQL
└ - - - - - -┘
```

Autrement dit, un Nginx sert de frontale web et transmet les requêtes à un serveur applicatif Gunicorn, qui communique avec la base de données (BDD) PosgreSQL;
Autrement dit, un Nginx sert de frontale web et transmet les requêtes à un serveur applicatif Gunicorn qui communique avec la base de données (BDD) PosgreSQL (pour les requêtes d'API), ou à un serveur Node (pour les requêtes client).

Par ailleurs :

* Gunicorn est géré par le _process manager_ `supervisor`, ce qui permet notamment d'assurer son redémarrage en cas d'arrêt inopiné.
* Gunicorn et Node sont gérés par le _process manager_ `supervisor`, ce qui permet notamment d'assurer leur redémarrage en cas d'arrêt inopiné.
* Le lien entre Gunicorn et la base de données PostgreSQL est paramétrable (_database URL_). Cette dernier ne vit donc pas nécessairement sur la même machine que le serveur applicatif (voir [Environnements](#environnements)).

## Démarrage rapide
Expand Down Expand Up @@ -149,6 +151,10 @@ $ pyenv --version
pyenv 2.2.3
$ python -V
Python 3.8.9
$ nvm --version
0.39.1
$ node -v
v16.13.2
$ systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy serv>
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor >
Expand All @@ -168,10 +174,12 @@ cd ops
make deploy-test
```

Vérifiez le bon déploiement en accédant à l'API depuis la VM Vagrant :
Vérifiez le bon déploiement en accédant au site ou à l'API depuis la VM Vagrant :

```console
$ curl localhost/datasets/
$ curl localhost
<!-- Du HTML ... -->
$ curl localhost/api/datasets/
[]
```

Expand All @@ -187,7 +195,7 @@ Une bonne pratique pour limiter les risques : déployer la migration d'abord, pu

### Nginx renvoie une "502 Bad Gateway"

Il y a probablement soit un problème de configuration de la connexion entre Nginx et Gunicorn (ex : mauvais port), soit le serveur Gunicorn n'est pas _up_ (ex : il crashe ou ne démarre pas pour une raison à déterminer).
Il y a probablement soit un problème de configuration de la connexion entre Nginx et Node / Gunicorn (ex : mauvais port), soit le serveur Node / Gunicorn n'est pas _up_ (ex : il crashe ou ne démarre pas pour une raison à déterminer).

* Vérifier l'état de Nginx :

Expand All @@ -201,7 +209,7 @@ Il y a probablement soit un problème de configuration de la connexion entre Ngi
~/catalogage $ systemctl status supervisor
```

* Vérifier l'état du processus serveur au sein de Supervisor :
* Vérifier l'état du processus serveur (`server` pour Gunicorn, `client` pour le frontend Node) au sein de Supervisor :

```
~/catalogage $ sudo supervisorctl status server
Expand Down Expand Up @@ -237,3 +245,9 @@ N.B. : cette commande demandera le mot de passe Vault associé à l'environnemen
On utilise [pyenv](https://github.com/pyenv/pyenv) pour installer Python sur les serveurs distants.

La configuration est aussi gérée par Ansible (rôle `pyenv`), notamment au moyen de la variable `pyenv_python_version`. En la modifiant, on peut ainsi mettre Python à jour. Il sera bien sûr préférable de s'assurer de retirer toute ancienne version de Python après une telle opération.

### Version de Node

De la même façon, on utilise [nvm](https://github.com/nvm-sh/nvm) pour installer Node.

La version est paramétrée par la variable `nvm_node_version`.
6 changes: 5 additions & 1 deletion ops/ansible/playbook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
vars:
workdir: "{{ ansible_env.HOME }}/catalogage"
pyenv_python_version: "3.8.9"
git_repo: "https://github.com/multi-coop/catalogage-donnees.git"
nvm_node_version: "v16.13.2"
git_repo: "https://github.com/etalab/catalogage-donnees.git"
git_version: "{{ env_branch }}"
web_port: 80
api_port: 3579
client_port: 3000

roles:
- common
Expand Down
5 changes: 5 additions & 0 deletions ops/ansible/roles/common/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
import_role:
name: pyenv

- name: Setup Node
# Use nvm to be able to install a specific Node version too.
import_role:
name: nvm

- name: Ensure nginx is installed and up to date
apt: name=nginx state=latest
become: true
Expand Down
10 changes: 10 additions & 0 deletions ops/ansible/roles/nvm/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
# Installation paths
nvm_home: "{{ ansible_env.HOME }}"
nvm_root: "{{ ansible_env.HOME }}/.nvm"

# nvm version to install
nvm_version: "v0.39.1"

# Node.js version to install
nvm_node_version: "v16.13.2"
6 changes: 6 additions & 0 deletions ops/ansible/roles/nvm/tasks/install.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- name: Install nvm
shell: >
curl https://raw.githubusercontent.com/nvm-sh/nvm/{{ nvm_version }}/install.sh | bash
args:
chdir: "{{ nvm_home }}"
creates: "{{ nvm_root }}/nvm.sh"
5 changes: 5 additions & 0 deletions ops/ansible/roles/nvm/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- name: Install nvm
import_tasks: install.yml

- name: Install Node version
import_tasks: node.yml
37 changes: 37 additions & 0 deletions ops/ansible/roles/nvm/tasks/node.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
- name: Install Node binary
shell: >-
source {{ nvm_root }}/nvm.sh && nvm install {{ nvm_node_version }}
args:
executable: /bin/bash
creates: "{{ nvm_root }}/versions/{{ nvm_node_version }}"

# Get Node and npm bin paths, taking into account that Ansible does not run
# commands as a user by default.
# See: https://stackoverflow.com/a/58744872
- name: Get node binary path
shell: bash -ilc 'which node'
register: which_node
- name: Set node_bin_path fact
set_fact:
node_bin_path: "{{ which_node.stdout | trim() }}"
- name: Get npm binary path
shell: bash -ilc 'which npm'
register: which_npm
- name: Set npm_bin_path fact
set_fact:
npm_bin_path: "{{ which_npm.stdout | trim() }}"
# Create symlinks to node/npm. This step is required so that Ansible
# commands can access them without having to refer to the full binary paths.
# (Nvm shims are only available through a shell, which, again, Ansible does not do by default.)
- name: Create node bin symlink
file:
src: "{{ node_bin_path }}"
dest: /usr/local/bin/node
state: link
become: true
- name: Create npm bin symlink
file:
src: "{{ npm_bin_path }}"
dest: /usr/local/bin/npm
state: link
become: true
4 changes: 4 additions & 0 deletions ops/ansible/roles/web/tasks/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- name: Ensure build is up to date
shell:
cmd: make build
chdir: "{{ workdir }}"
6 changes: 6 additions & 0 deletions ops/ansible/roles/web/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
- name: Sync Python dependencies
import_tasks: python.yml

- name: Sync Node dependencies
import_tasks: node.yml

- name: Sync build
import_tasks: build.yml

- name: Sync DB
import_tasks: db.yml

Expand Down
6 changes: 6 additions & 0 deletions ops/ansible/roles/web/tasks/node.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- name: Ensure Node dependencies are installed
npm:
path: "{{ workdir }}/client"
ci: true
state: present
become: true
5 changes: 5 additions & 0 deletions ops/ansible/roles/web/tasks/supervisor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
# Config or dependencies may have changed, so restart the server.
notify: reload supervisor

- name: Ensure client is running
supervisorctl: name=client state=present
become: true
notify: reload nginx

- name: Ensure server is running
supervisorctl: name=server state=present
become: true
Expand Down
17 changes: 14 additions & 3 deletions ops/ansible/roles/web/templates/nginx.conf.j2
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
upstream client {
server 127.0.0.1:{{ client_port }};
}

upstream api {
server 127.0.0.1:3579;
server 127.0.0.1:{{ api_port }};
}

server {
listen 80;
listen {{ web_port }};
server_name {{ inventory_hostname }};

location /favicon.ico {
Expand All @@ -13,6 +17,13 @@ server {

location / {
include proxy_params;
proxy_pass http://api;
proxy_pass http://client;
}

location /api/ {
include proxy_params;
{# NOTE: trailing / is important. Ensures /api/foo becomes /foo. #}
{# See: http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass #}
proxy_pass http://api/;
}
}
11 changes: 10 additions & 1 deletion ops/ansible/roles/web/templates/supervisor.conf.j2
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
[program:client]
directory={{ workdir }}/client
command=npm start
environment=HOST=127.0.0.1,PORT="{{ client_port }}"
autostart=true
autorestart=true
stderr_logfile=/var/log/client.err.log
stdout_logfile=/var/log/client.out.log

[program:server]
directory={{ workdir }}
command={{ workdir }}/venv/bin/gunicorn -w 2 --bind 127.0.0.1:3579 -k uvicorn.workers.UvicornWorker server.main:app
command={{ workdir }}/venv/bin/gunicorn -w 2 --bind 127.0.0.1:{{ api_port }} -k uvicorn.workers.UvicornWorker server.main:app
autostart=true
autorestart=true
stderr_logfile=/var/log/server.err.log
Expand Down

0 comments on commit 98511a2

Please sign in to comment.