deploy_100-1526476478430
deploy_100-1526476478430
Goal
The aim of this training is to give good practice to deploy and maintain an odoo deployment, using a Linux system,
for small business to bigger ones, from one system with multiple odoo to one odoo on multiple systems.
There’s many ways to deploy odoo, from a .deb on a virtual machine to multiple docker containers,
from a self hosted server to a cloud instance. We’ll use the recommended way to use a less layers as possible,
as every layer you add require to master it.
Schedule
Day 1 Day 2
Install PostgreSQL Adanced NGinx
Install Odoo PostgreSQL Configuration
Install NGinx Data Replication
Odoo Configuration Load balancing
Odoo command line Etherpad
Exercices Backup
Monitor
One server
Easy to understand and deploy, it’s the most common case. 1 instance or multiple instances
Multiple servers
Harder to deploy and maintain, require higher sysadmin skills, recommended for fault tolerance and larger business.
And a mix
Between those two configurations, there’s plenty of deployment scenarios, depending on the needs.
Always remember that you’ll need a production system, and a testing system.
Testing system does not have to be the same that the production one, but must use same architecture (OS, …).
Don’t use a shared server between test and production env, to avoid performance bottleneck while testing,
permit a restart on test without stopping production, …
KISS
Keep it stupid and simple
Remember that you need a stable and maintainable system. Every layer you add require knowledge.
Don’t add unnecessary layers. En example can be a replicated database with nobody able to monitor it
or easy recover from a failure, causing a bigger downtime in case of failure than in a single server scenario.
SSH
Make server available by ssh with key authenticationMake server available by ssh with key authentication. You should :
1 $ sudo vi /etc/ssh/sshd_config
1 AuthorizedKeysFile %h/.ssh/authorized_keys
2 PubkeyAcceptedKeyTypes ssh-dss
You can choose to use dsa or rsa, the key must be added to your github account, and must be password protected.
1 PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games"
2 LC_ALL=en_US.UTF-8
3 LANG=en_US.UTF-8
Install PostgreSQL
Use apt.postgresql.org
You can choose to use the distribution repo to install PostgreSQL, or the official PostgreSQL apt repository to install
a more recent version of PostgreSQL. Ubuntu has only one PostgreSQL version per release in official repo.
We’ll use the PostgreSQL one here.
1 $ wget https://anonscm.debian.org/cgit/pkg-postgresql/postgresql-common.git/plain/pgdg/apt.postgresql.org.sh
2 $ sudo bash apt.postgresql.org.sh
Install PostgreSQL
You’ll need the postgresql server itself, the client lib, and pg-activity to check running queries on a real-time basis.
1 $ sudo apt-get install postgresql-9.6 postgresql-client-9.6 \
2 postgresql-client-common postgresql-common \
3 postgresql-server-dev-9.6 pg-activity
A PostgreSQL user is required by Odoo to connect. For now, we’ll use same PostgreSQL username as the unix one to use unix socket.
1 $ sudo su - postgres
2 postgres> psql
3 postgres=# CREATE USER odoo WITH LOGIN CREATEDB;
4 postgres=# \q
5 postgres> exit
6 $
Install Odoo
We’ll deploy Odoo from git sources
git clone
We’ll use /opt/odoo10 as base dir.
1 $ cd /opt
2 $ sudo mkdir odoo10
3 $ sudo chown odoo odoo10
4 $ cd odoo10
5 $ git clone [email protected]:odoo/odoo.git
6 OR
7 $ git clone -b 10.0 --single-branch [email protected]:odoo/odoo.git
Odoo prerequisites
Odoo have some required python packages (like the web server, db connection library, …). Those python packages needs
some C/C++ headers to compile. We install the required C/C++ dev libraries from distribution repo, then we use pip
to install the Odoo python required packages.
First launch
We can now launch odoo to check if all is ok and create a first database. We’ve deployed all required dependencies,
we’ll just use one option on the command line to explain where we can find addons. Enterprise addons are set before
the community ones!
1 $ cd /opt/odoo10/odoo
2 $ ./odoo.py --addons-path=/opt/odoo10/enterprise,/opt/odoo10/odoo/addons
WKHtmlToPdf
To print reports, odoo generates an html, send it to wkhtmltopdf which is in charge of the html->pdf transformation.
NGinx
Install NGINX
1 $ sudo apt-get install nginx
We’ll assume that our server name is odoo.training.internal, and that our DNS is well configured to handle this.
Locally, for training purpose, we can simply modify our hosts file.
1 #odoo server
2 upstream odoo {
3 server 127.0.0.1:8069;
4 }
5
6 server {
7 listen 80;
8 server_name odoo.training.internal;
9
10 # Add Headers for odoo proxy mode
11 proxy_set_header X-Forwarded-Host $host;
12 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
13 proxy_set_header X-Forwarded-Proto $scheme;
14 proxy_set_header X-Real-IP $remote_addr;
15
16 #log
17 access_log /var/log/nginx/odoo.access.log;
18 error_log /var/log/nginx/odoo.error.log;
19
20 # Redirect requests to odoo backend server
21 location / {
22 proxy_redirect off;
23 proxy_pass http://odoo;
24 }
25 }
Re-Launch NGINX
Odoo Seems to run behind NGINX. Proxy-mode because odoo is behing nginx. Forwarded headers are very important because
Odoo only see 1 client : nginx!
Odoo as a service
using Systemd, we’ll start odoo at system staretup.
1 $ sudo vi /lib/systemd/system/odoo.service
1 [Unit]
2 Description=odoo
3 Requires=postgresql.service
4 After=postgresql.service
5 Wants=nginx.service
6
7 [Install]
8 Alias=odoo.service
9
10 [Service]
11 Type=simple
12 User=odoo
13 Group=odoo
14 SyslogIdentifier=odoo-server
15 PIDFile=/run/odoo/odoo-server.pid
16 ExecStart=/opt/odoo10/odoo/odoo.py --config=/etc/odoo/odoo10.conf
17
18 [Install]
19 WantedBy=multi-user.target
1 [Unit]
2 After=network.target
1 [options]
2 addons_path = /opt/odoo10/enterprise,/opt/odoo10/odoo/addons
3 proxy_mode = True
Configure Odoo
Add logging
By default, Odoo displays all logging of level info except for workflow logging (warning only), and log output is sent to stdout
1 [options]
2 addons_path = /opt/odoo10/enterprise,/opt/odoo10/odoo/addons
3 proxy_mode = True
4 logfile = /var/log/odoo/odoo.log
5 syslog = False
6 log_db = False
Log rotation
In some cases, it’s not a good idea to use the odoo log rotation internal mechanism, because 2 workers can try to rotate logs at the same time.
To achieve such a goal, we use the logrotate daemon. We’ll also need the pidfile for odoo
1 [options]
2 ...
3 logrotate = False
4 pidfile = /var/run/odoo.pid
1 /var/log/odoo/odoo.log {
2 rotate 10
3 daily
4
5 # Do not rotate the log if it is empty
6 notifempty
7
8 # if the log file is missing, go on to the next one without
9 # issuing an error message.
10 missingok
11
12 compress
13 delaycompress
14
15 postrotate
16 if [ -f /var/run/odoo.pid ]; then
17 kill -HUP `cat /var/run/odoo.pid`
18 fi
19 endscript
20 }
Check logs
As logs are sent to a file, here’s how to display them in real-time
1 $ tail -f /var/log/odoo/odoo.log
To search within the log, for a regex, and display line number, 2 lines after and 5 lines before the result
1 [options]
2 ...
3 log_handler = openerp.sql_db:DEBUG,werkzeug:DEBUG,openerp.fields:WARNING,openerp.http.rpc.request:DEBUG,:WARNING
Log level
log levels are shortcut for log handlers, to ease configuration of most commonly used handlers combination : critical, error, warn, debug,
debug_sql, debug_rpc, debug_rpc_answer
1 [options]
2 ...
3 log_level = debug_rpc
Odoo workers
Only on unix
Python GIL: in CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python
bytecodes at once.
Add workers
1 worker ~= 6 users
# workers = (#cpus * 2 ) + 1
1 [options]
2 ...
3 workers = 4
4 max_cron_threads = 1
Check in log :
1 [...]
2 upstream odoo_longpoll {
3 server 127.0.0.1:8072;
4 }
5 [...]
6 location /longpoll {
7 proxy_redirect off;
8 proxy_pass http://odoo_longpoll;
9 }
10 [...]
CPU
limit_time_cpu = 60
limit_time_real = 120
limit_time_real_cron = limit_time_real New since V10
If a worker reach the limit, the process is killed, without sending the response to the client.
Memory
limit_memory_soft = 2 Gb
A worker which reach this memory limit during the processing of the request is recycled after sending the response.
limit_memory_hard = 2.5 Gb
A worker which reach this memory limit during the processing of the request is recycled immediatly, without sending the response.
Requests count
limit_request = 8192
A worker which process this # of request is recycled at the end of the request response
1 [options]
2 ...
3 limit_time_cpu = 60
4 limit_time_real = 240
5 limit_time_real_cron = 3600
6 limit_memory_soft = 2147483648
7 limit_memory_hard = 4294967296
8 limit_request = 8192
Test workers recycling, soft and hard… You can set for example, limit_request to 2 or any other stupid value
Wizard limits
Wizard are object defined as not-persistent within Odoo. Those objects are in fact stored within the database and
cleaned with a cron. By default, cron clean those records after one hour. You can choose to clean faster/slower and/or
limit the number of such records within the database.
osv_memory_count_limit is a per table setting, while osv_memory_age_limit (expressed in hours) is for all wizards.
1 [options]
2 ...
3 osv_memory_count_limit = 100000
4 osv_memory_age_limit = 4
DB Filtering
When using website, it’s required to use db filtering. dbfilter is a regex, which can contain %d and %h special values, representing respectively
the server name and the FQDN.
dbfilter
regex
%h
%d
1 ...
2 dbfilter = ^%d[0-9]*$
DB admin
Avoid anyone to drop your db’s, stole your datas,… via /web/database/manager
admin_passwd
1 ...
2 admin_passwd = ApRmoGy+DcFREAVCkD7PbtznKc1KDsyU
DB Connection
Manage connection to the server. Using uniw sockets by default (or localhost on windows)
1 [options]
2 ...
3 db_name = False
4 db_host = False
5 db_port = 5432
6 db_user = False
7 db_password = False
8 db_template = template1
9 db_maxconn = 4
List DB
By default, a user can choose db to connect to. list_db can disable this feature.
1 [options]
2 ...
3 list_db = False
1 [options]
2 ...
3 unaccent = False
4 geoip_database = /usr/share/GeoIP/GeoLiteCity.dat
Data dir
Some binaries are stored on the file system, instead of storing them in DB. Default is
home_drive/.local/share/odoo/version, C:\Documents and Settings\\Application Data\… on Windows, …
1 [options]
2 ...
3 data_dir = /home/odoo/datas
IP ports
You can change the ip ports used
1 [options]
2 ...
3 xmlrpc = True
4 xmlrpc_interface = 127.0.0.1
5 xmlrpc_port = 8069
6 longpolling_port = 8072
SMTP
You can configure a default mail server. By default, Odoo use localhost on port 25, without SSL nor authentication
1 [options]
2 ...
3 email_from = [email protected]
4 smtp_server = 127.0.0.1
5 smtp_port = 25
6 smtp_ssl = False
7 smtp_user = False
8 smtp_password = False
Demo data
It’s better on production server to avoid deploying demo datas… (comma-separated, use \“all\” for all modules) Used with -d and -i
1 [options]
2 ...
3 without_demo = all
Debug mode
Known from Odoo developers, this mode alter a bit the datadriven mode of Odoo, by reading files from disks instead of database
1 [options]
2 ...
3 debug_mode = False
Big imports
Use this for big data importation, if it crashes you will be able to continue at the current state. Provide a filename to store intermediate
importation states.
1 [options]
2 ...
3 import_partial = /user/odoo/tempimport
Pid File
Store the pid within a file
1 [options]
2 ...
3 pidfile = /run/odoo/odoo-server.pid
1 [options]
2 ...
3 pidfile = False
1 [options]
2 ...
3 server_wide_modules = web,mysupermodule
A use case for those kind of modules are the hw_xxx modules of the posbox
Per-DB Odoo Configuration
ir.config_parameter
It’s possible to change some settings on a per-database basis. Those parameters permit to solve some issues
depending on the deployment senario.
web.base.url
web.base.url is the base url of your Odoo deployment. You can change it in the ir.config_parameter table.
The default behaviour of Odoo is to reset the value of this parameter each time the admin logs in.
Logging in with admin using an ssh tunnel can broke some features…
pdf reports
Here are the steps when odoo prints a pdf:
report.url
wkhtmltopdf can request informations (assets) directly from Odoo, without contacting reverse proxy.
In some scenarios, when web.base.url is resolved as the firewall ip for example, the firewall detects a software in the
lan requesting an url in the lan, then drop the request. This makes the wkhtmltopdf request for assets fails, and thus
the generated pdf lacks of css.
the report.url can be set to _ http://localhost:8069 _ to enable direct connection from wkhtmltopdf to Odoo.
in /etc/odoo/odoo10.conf :
1 #logfile = /var/log/odoo/odoo.log
in /lib/systemd/system/odoo.service :
Exercices
2 instances
Without altering the running server, it’s asked to deploy as a service, and only accessible through NGinx:
1 instance, 2 customers
Without altering the running servers, it’s asked to deploy as a service, and only accessible through NGinx, one odoo 10 enterprise serving two
customers, with 3 workers and 1 cron, on ports 8079 and 8082:
FQDN DB
cust1.training.internal cust1
cust2.training.internal cust2
Logging
Set logging on cust1 to debug the rpc responses.
Set logging on cust2 to debug the rpc queries, debug sql queries, and only critical on others.
Set logging on the odoo9 instance to debug only the crm module, and info on others.
New module
On the Odoo9 instance, deploy one OCA module, accessible through github, and install it on database.
Advanced NGINX
SSL
For now, Odoo is running in clear http. We need to redirect http requests to https ones.
1 #SSL
2 server {
3 listen 443;
4 server_name odoo.training.internal;
5
6 ssl on;
7 ssl_certificate /etc/ssl/nginx/server.crt;
8 ssl_certificate_key /etc/ssl/nginx/server.key;
9 ssl_session_timeout 30m;
10 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
11 ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-
12 ssl_prefer_server_ciphers on;
13
14 [...]
Pos
If you’re using posbox, as the browser connect to the posbox using http, you’ve to publish the pos using http
to avoid a mixed content error within the browser.
1 server {
2 listen 80;
3 [...]
4 #Redirect non-POS and related content in https to allow POS in http
5 location ~ ^(?!/pos/|/web/binary/company_logo|/?[^/]*/website/action/|/web/webclient/|/web/content/|/web_editor/s
6 proxy_redirect off;
7 if ($request_method != POST) {
8 # rewriting a POST request has no sense as the browser will do the request as GET
9 rewrite ^(.*)$ https://$host$1 permanent;
10 }
11 if ($request_method = POST) { proxy_pass http://odoo; }
12 }
13
14 location = /longpolling/poll { proxy_redirect off; proxy_pass http://odoo_longpoll;}
15 location / { proxy_redirect off; proxy_pass http://odoo; }
16 }
17 [...]
18 server {
19 listen 443;
20 [...]
21 # Redirect POS to http
22 location ~ ^/pos/web {
23 rewrite ^(.*) http://$host:80$1 permanent;
24 }
25 # Redirect requests to odoo backend server
26 location / {
27 proxy_redirect off;
28 proxy_pass http://odoo;
29 }
30 location /longpolling {
31 proxy_redirect off;
32 proxy_pass http://odoo_longpoll;
33 }
34 }
Full NGinx config
1 #odoo server
2 upstream odoo {
3 server 127.0.0.1:8069;
4 }
5 upstream odoo_longpoll {
6 server 127.0.0.1:8072;
7 }
8
9 proxy_set_header X-Forwarded-Host $host;
10 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
11 proxy_set_header X-Forwarded-Proto $scheme;
12 proxy_set_header X-Real-IP $remote_addr;
13 proxy_set_header Host $http_host;
14 add_header X-Content-Type-Options nosniff;
15
16 server {
17 listen 80;
18 server_name odoo.training.internal;
19
20 access_log /var/log/nginx/odoo.access.log;
21 error_log /var/log/nginx/odoo.error.log;
22
23 #Redirect non-POS and related content in https to allow POS in http
24 location ~ ^(?!/pos/|/web/binary/company_logo|/?[^/]*/website/action/|/web/webclient/|/web/content/|/web_editor/s
25 proxy_redirect off;
26
27 if ($request_method != POST) {
28 # rewriting a POST request has no sense as the browser will do the request as GET
29 rewrite ^(.*)$ https://$host$1 permanent;
30 }
31 if ($request_method = POST) {
32 proxy_pass http://odoo;
33 }
34 }
35 location = /longpolling/poll {
36 proxy_redirect off;
37 proxy_pass http://odoo_longpoll;
38 }
39
40 location / {
41 proxy_redirect off;
42 proxy_pass http://odoo;
43 }
44 }
45
46 server {
47 listen 443;
48 server_name odoo.training.internal
49
50 access_log /var/log/nginx/odoossl.access.log;
51 error_log /var/log/nginx/odoossl.error.log;
52
53 # SSL parameters
54 ssl on;
55 ssl_certificate /etc/ssl/nginx/server.crt;
56 ssl_certificate_key /etc/ssl/nginx/server.key;
57 ssl_session_timeout 30m;
58 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
59 ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES
60 ssl_prefer_server_ciphers on;
61
62 # Redirect POS to http
63 location ~ ^/pos/web {
64 rewrite ^(.*) http://$host:80$1 permanent;
65 }
66
67 # Redirect requests to odoo backend server
68 location / {
69 proxy_redirect off;
70 proxy_pass http://odoo;
71 }
72 location /longpolling {
73 proxy_redirect off;
74 proxy_pass http://odoo_longpoll;
75 }
76 }
PostgreSQL configuration
Until now, we’ve used to deploy odoo and postgresql on the same server. We will now deploy PostgreSQL on his own server, and configure it.
Use VM odoo-pg1
We will use a new VM and the private network.
Copy private/public ssh keys from odoo server, alter config, add hosts, …
1 $ sudo vi /etc/ssh/sshd_config
1 AuthorizedKeysFile %h/.ssh/authorized_keys
2 PubkeyAcceptedKeyTypes ssh-dss
1 $ sudo vi /etc/hosts
1 192.168.56.10 odoo-training
2 192.168.56.22 odoo-pg2
3 192.168.56.23 odoo-pg3
Use apt.postgresql.org
Deploy it like in the single server scenario
1 $ wget https://anonscm.debian.org/cgit/pkg-postgresql/postgresql-common.git/plain/pgdg/apt.postgresql.org.sh
2 $ sudo bash apt.postgresql.org.sh
3 $ sudo apt-get install postgresql-9.6 postgresql-client-9.6 \
4 postgresql-client-common postgresql-common \
5 postgresql-server-dev-9.6 pg-activity
6 $ sudo su - postgres
1 postgres> psql
2 postgres=# CREATE USER odoo WITH LOGIN CREATEDB PASSWORD 'OdooPostgrePWD';
3 postgres=# \q
4 postgres> exit
5 $
1 ...
2 # IPv4 local connections:
3 host all all 127.0.0.1/32 md5
4 host all all 192.168.56.0/24 md5
1 $ sudo vi /etc/postgresql/9.6/main/postgresql.conf
1 ...
2 listen_addresses = '192.168.56.21'
1 ...
2 After=network.target
1 db_host = odoo-pg1
2 db_port = 5432
3 db_user = odoo
4 db_password = OdooPostgrePWD
# connections
This is very important to some of the below parameters (particularly work_mem) because there are some memory resources that are or can be
allocated on a per-client basis, so the maximum number of clients suggests the maximum possible memory use
1 $ sudo vi /etc/postgresql/9.6/main/postgresql.conf
1 ...
2 max_connections = 80
shared_buffers
The shared_buffers configuration parameter determines how much memory is dedicated to PostgreSQL to use for caching data
1 $ sudo vi /etc/postgresql/9.6/main/postgresql.conf
1 ...
2 shared_buffers = 1GB
You can retrieve the value the OS can allocate to PostgreSQL in monitoring systems like Munin
effective_cache_size
effective_cache_size should be set to an estimate of how much memory is available for disk caching[…]
1 $ sudo vi /etc/postgresql/9.6/main/postgresql.conf
1 ...
2 effective_cache_size = 1GB
work_mem
If you do a lot of complex sorts, and have a lot of memory, then increasing the work_mem parameter allows PostgreSQL
to do larger in-memory sorts which, unsurprisingly, will be faster than disk-based equivalents. This parameter is a
per connection value!
1 $ sudo vi /etc/postgresql/9.6/main/postgresql.conf
1 ...
2 work_mem = 64MB
autovacuum
Required to:
WAL size
Prior to 9.5, parameter was checkpoint_segments. It has been replaced in 9.5 with min_wal_size and max_wal_size
Note that the default setting for max_wal_size is much higher than the default checkpoint_segments used to be,
so adjusting it might no longer be necessary.
Even if NFS is not designed for performance, it’s an easy way to achieve our goal, and due to the way Odoo handles
files I/O, this will not be an issue.
NFS server
On odoo-pg1, we deploy NFS server and allow connections from odoo servers.
NFS client
On odoo-training, we deploy NFS client.
1 ...
2 # Odoo filestore
3 odoo-pg1:/export/odoo /filestore nfs rw,hard,intr 0 0
1 ...
2 data_dir = /filestore
Datas Replication
Rebuilding an Odoo server from scratch when all datas are stored on another server is quite fast and easy,
rebuilding a data server (PostgreSQL and filestore) is a bit harder.
Use VM odoo-pg2
We will use a new VM.
Copy private/public ssh keys from odoo server, alter config, add hosts, …
1 $ sudo vi /etc/ssh/sshd_config
1 AuthorizedKeysFile %h/.ssh/authorized_keys
2 PubkeyAcceptedKeyTypes ssh-dss
1 $ sudo vi /etc/hosts
1 192.168.56.10 odoo-training
2 192.168.56.21 odoo-pg1
3 192.168.56.23 odoo-pg3
Use apt.postgresql.org
Deploy it like on odoo-pg1
1 $ wget https://anonscm.debian.org/cgit/pkg-postgresql/postgresql-common.git/plain/pgdg/apt.postgresql.org.sh
2 $ sudo bash apt.postgresql.org.sh
3 $ sudo apt-get install postgresql-9.6 postgresql-client-9.6 \
4 postgresql-client-common postgresql-common \
5 postgresql-server-dev-9.6 pg-activity
Promote odoo-pg1
First, we’ll promote odoo-pg1 as replication master.
1 ...
2 wal_level = hot_standby
3 max_wal_senders = 3
4 wal_keep_segments = 8
5 archive_mode = on
6 full_page_writes = on
7 archive_command = 'cp "%p" "/var/lib/postgresql/9.6/shipped/%f"'
1 ...
2 hostssl replication odoo_rep 192.168.56.0/24 md5
1 $ sudo su - postgres
2 $ mkdir /var/lib/postgresql/9.6/shipped/
3 $ exit
Initiate replication
It’s now time to initiate replication from odoo-pg1 to odoo-pg2. We do the same config as on odoo-pg1, the main difference is the hot_standby
parameter.
1 $ sudo vi /etc/postgresql/9.6/main/postgresql.conf
1 ...
2 listen_addresses = '192.168.56.22'
3 max_connections = 80
4 shared_buffers = 1GB
5 effective_cache_size = 1GB
6 work_mem = 64MB
7 wal_level = hot_standby
8 max_wal_senders = 3
9 wal_keep_segments = 8
10 full_page_writes = on
11 archive_command = 'cp "%p" "/var/lib/postgresql/9.6/shipped/%f"'
12 hot_standby = on
Allow connections
Even if not required, it’s a good idea to permit connections to this slave the same way it is on the master. It will be easier in case of failover.
1 $ sudo vi /etc/postgresql/9.6/main/pg_hba.conf
1 ...
2 # IPv4 local connections:
3 host all all 127.0.0.1/32 md5
4 host all all 192.168.56.0/24 md5
5 ...
6 hostssl replication odoo_rep 192.168.56.0/24 md5
recovery.conf
We now configure odoo-pg2 To connect to odoo-pg1 to stream replicate.
1 $ sudo vi /etc/postgresql/9.6/main/recovery.conf
1 standby_mode = 'on'
2 primary_conninfo = 'host=odoo-pg1 port=5432 user=odoo_rep password=ByrXga8IbnL5hNTeB46tj41e3B7edPs sslmode=require'
3 trigger_file = '/tmp/postgresql.failover'
1 $ sudo su - postgres
2 $ mkdir /var/lib/postgresql/9.6/shipped/
3 $ exit
Replication script
We will now destroy cluster on odoo-pg2 and dump the one from odoo-pg1 to start replication.
1 $ sudo vi ~/replicate.sh
1 -[ RECORD 1 ]----+------------------------------
2 pid | 8721
3 usesysid | 24029
4 usename | odoo_rep
5 application_name | walreceiver
6 client_addr | 192.168.56.22
7 client_hostname |
8 client_port | 38268
9 backend_start | 2016-09-01 17:01:38.184454+02
10 backend_xmin |
11 state | streaming
12 sent_location | 0/23000060
13 write_location | 0/23000060
14 flush_location | 0/23000060
15 replay_location | 0/23000000
16 sync_priority | 0
17 sync_state | async
Replicate filestore
We will now replicate filestore to have a fully redundant data store. We’ll use rsync, but other solutions based on clustered file systems, like
drbd, are possible. On odoo-pg2:
1 #!/bin/bash
2 # Replicate odoo file store
3 rsync -az odoo@odoo-pg1:/export/ /export
1 $ crontab -e
1 */5 * * * * /home/odoo/syncfilestore.sh
Deploy odoo-pg3
As an exercice, deploy another slave postgresql server. Use odoo-pg3 VM for this purpose.
Load balancing
Before adding more Odoo server to handle a higher load, we need something to distribute requests across the different odoo servers :
HAProxy
Deploy HAProxy VM
Deploy VM as usual :
Network card
/etc/hosts
SSH
Locale
SSL certificate
Use at least let’s encrypt… Here we’ll use a self signed as no public ip routing.
Configure HAProxy
1 $ sudo vi /etc/haproxy/haproxy.cfg
1 global
2 log /dev/log local1 warning
3 stats socket /run/haproxy/admin.sock mode 660 level admin
4 stats timeout 30s
5 user haproxy
6 group haproxy
7 daemon
8 maxconn 3000
9 ca-base /etc/ssl/certs
10 crt-base /etc/ssl/private
11 ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+A
12 ssl-default-bind-options no-sslv3 no-tls-tickets
13 tune.ssl.default-dh-param 2048
14
15 defaults
16 log global
17 mode http
18 option httplog
19 option dontlognull
20 timeout connect 5000ms
21 timeout client 900s
22 timeout server 900s
23 timeout check 90s
24
25 listen stats
26 bind *:1936
27 mode http
28 stats enable
29 stats hide-version
30 stats realm Haproxy\ Statistics
31 stats auth odoo:odoo
32 stats uri /haproxy-status
33
34 frontend odoo_no_ssl
35 bind *:80
36 maxconn 3000
37 option http-server-close
38 option forwardfor
39 reqadd X-Forwarded-Proto:\ http
40 reqadd X-Forwarded-Port:\ 80
41 default_backend backend_odoo_no_ssl
42
43 frontend odoo_ssl
44 bind *:443 ssl crt /etc/haproxy/odoo.pem
45 maxconn 3000
46 option http-server-close
47 option forwardfor
48 reqadd X-Forwarded-Proto:\ https
49 reqadd X-Forwarded-Port:\ 443
50 default_backend backend_odoo_ssl
51
52 backend backend_odoo_no_ssl
53 mode http
54 balance roundrobin
55 option httpchk HEAD / HTTP/1.0\r\nHost:\ odoo.training.internal
56 default-server inter 10s rise 1 fall 3
57 http-check send-state
58 server odoo1 192.168.56.10:80 check
59 server odoo2 192.168.56.11:80 check
60
61 backend backend_odoo_ssl
62 mode http
63 balance roundrobin
64 option httpchk HEAD / HTTP/1.0\r\nHost:\ odoo.training.internal
65 default-server inter 10s rise 1 fall 3
66 http-check send-state
67 server odoo1 192.168.56.10:443 ssl verify none check check-ssl
68 server odoo2 192.168.56.11:443 ssl verify none check check-ssl
Test HAProxy
Change IP in your host file…
Sessions cleaning
Avoid having odoo itself cleaning sessions
Every ~1000 requests, Odoo list sessions, and remove the too old ones.
As it’s not a problem on a local filesystem, this can be problematic on a shared filesystem, like the NFS one we’re using.
To avoid this performance bottleneck, we’ll stop to check this with Odoo itself, and use a linux cron to achieve this.
in session.py:
1 import session
1 $ crontab -e
clone VM in virtualbox
sudo vi /etc/hostname
sudo vi /etc/hosts
sudo vi /etc/network/interfaces
To ensure a proper bundle generation, all dates must be identical between the servers. To achieve this, can have two
solutions:
A share drive
A rsync between the servers
As we already have deployed a share drive for filestore, it’s easy to achieve this for code.
On server odoo-1:
1 $ cd /opt/odoo10/odoo
2 $ git pull
3 $ cd /opt/odoo10/enterprise
4 $ git pull
5 [...]
6 $ cd /opt/odoo10/my_modules
7 $ git pull
8 [...]
Etherpad
node.js
1 $ sudo apt install -y build-essential
2 $ cd
3 $ curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
4 $ sudo apt-get install -y nodejs
Etherpad
1 $ sudo apt install libssl-dev pkg-config
2 $ cd /opt
3 $ sudo mkdir etherpad
4 $ sudo mkdir /var/log/etherpad
5 $ sudo chown odoo etherpad
6 $ sudo chgrp odoo etherpad
7 $ cd etherpad
8 $ git clone git://github.com/ether/etherpad-lite.git
9 $ sudo adduser etherpad --system --group --home /var/etherpad
10 $ sudo chown -R etherpad /var/log/etherpad
11 $ sudo chown -R etherpad etherpad-lite
12 $ cd etherpad-lite
13 $ ./bin/run.sh
1 $ sudo vi /opt/etherpad/etherpad-lite/settings.json
1 "trustProxy" : true,
2 "users": {
3 "admin": {
4 "password": "admin",
5 "is_admin": true
6 },
7 "user": {
8 "password": "user",
9 "is_admin": false
10 }
11 },
12 "defaultPadText" : "",
13 "suppressErrorsInPadText" : true,
14 "logconfig" :
15 { "appenders": [
16 /*{ "type": "console"
17 //, "category": "access"// only logs pad access
18 }*/
19 { "type": "file"
20 , "filename": "/var/log/etherpad/etherpad.log"
21 , "maxLogSize": 1024
22 , "backups": 3 // how many log files there're gonna be at max
23 //, "category": "test" // only log a specific category
24 }
1 $ sudo vi /lib/systemd/system/etherpad.service
1 [Unit]
2 Description=Etherpad-lite, the collaborative editor.
3 After=syslog.target network.target
4
5 [Service]
6 Type=simple
7 User=etherpad
8 Group=etherpad
9 WorkingDirectory=/opt/etherpad/etherpad-lite
10 ExecStart=/opt/etherpad/etherpad-lite/bin/run.sh
11 Restart=always
12
13 [Install]
14 WantedBy=multi-user.target
1 $ journalctl
Proxiing etherpad
1 $ sudo vi /etc/nginx/sites-available/pad
1 server {
2 listen 80;
3 server_name pad.training.internal;
4
5 #log
6 access_log /var/log/nginx/etherpad.access.log;
7 error_log /var/log/nginx/etherpad.error.log;
8
9 # Redirect requests to etherpad backend server
10 location / {
11 proxy_redirect off;
12 proxy_set_header Host $host;
13 proxy_pass http://127.0.0.1:9001;
14 }
15 }
Test it
Install module Pad on tasks
1 $ cat /opt/etherpad/etherpad-lite/APIKEY.txt
If etherpad is stopped
Test by stopping it
If description is empty
If task description is empty after filling it within the pad, that’s because odoo server cannot contact pad server
check name resolution on the odoo server. In this training, edit /etc/hosts on the odoo server
Backup
PostgreSQL
You’ve mainly 2 ways to do backups:
pg_dump
pg_basebackup
Each one have pros and cons. There’s no one size fits all solution, mainly dependant on :
pg_dump
The most used solution, permit to backup and restore databases one at a time.
Can also use text format, easy to migrate from one postgresql version to another (>=)
pg_basebackup
Backup the whole database cluster. Already used in training for starting streaming replication. Can be used to backup a database, and restore it
with WAL files to minimise RPO.
To incluide WAL files, create a recovery.conf file in the cluster data directory, containing:
Adapt the command to your configuration. If you choose this backup/restore method, train yourself to be ready!
Monitor
Munin require 1 master server which collects datas, and plenty of node where to collect datas from. We will use a new VM for the main munin
server.
Copy private/public ssh keys from odoo server, alter config, add hosts, …
1 $ sudo vi /etc/ssh/sshd_config
1 AuthorizedKeysFile %h/.ssh/authorized_keys
2 PubkeyAcceptedKeyTypes ssh-dss
1 $ sudo vi /etc/hosts
1 192.168.56.10 odoo-1
2 192.168.56.11 odoo-2
3 192.168.56.12 odoo-3
4 192.168.56.21 odoo-pg1
5 192.168.56.22 odoo-pg2
6 192.168.56.23 odoo-pg3
7 192.168.56.30 haodoo
This will install munin, as well as munin-node (for local server monitoring), some plugins, …
We need only one Munin, but we’ll need to deploy munin-node on each node to monitor.
Install NGinx
We’ll use NGinx to serve static files generated by munin. It’s also possible to use NGinx as a proxy to forward requests to munin-http.
1 server {
2 listen 80 default_server;
3 listen [::]:80 default_server;
4
5 location / {
6 rewrite ^/$ munin/ redirect; break;
7 }
8 location /munin/static/ {
9 alias /etc/munin/static/;
10 expires modified +1w;
11 }
12 location /munin/ {
13 auth_basic "Restricted";
14 # Create the htpasswd file with the htpasswd tool.
15 auth_basic_user_file /etc/nginx/htpasswd;
16
17 alias /var/cache/munin/www/;
18 expires modified +310s;
19 }
20 }
1 $ sudo vi /etc/munin/munin-node.conf
1 ...
2 host_name haodoo
3 allow ^192\.168\.56\.40$
4 ...
1 $ sudo vi /etc/munin/munin.conf
1 ...
2 [ha;haodoo]
3 address 192.168.56.30
4 ...
Munin is able to monitor pretty anything. The node has for job to measure and send datas to master. By default, some plugins are already
deployed (ram, cpu, …) but we can add somes more relevant to our system. A way to determine which plugins are usefull is to use munin-node-
configure itself :
Those 2 plugins seems relevant to our particular node. Let’s deploy them
1 ...
2 [haproxy_*]
3 env.url http://odoo:odoo@localhost:1936/haproxy-status?stats;csv
1 [ha;haodoo]
2 address 192.168.56.30
3 HAPActive.backend_odoo_ssl_act.warning 2:
4 HAPActive.backend_odoo_ssl_act.critical 1:
5 HAPActive.backend_odoo_no_ssl_act.warning 2:
6 HAPActive.backend_odoo_no_ssl_act.critical 1:
It’s also possible to add more plugins on node, from github for example, like the ones from https://github.com/munin-monitoring/contrib
1 $ sudo rm /etc/munin/plugins/haproxy_ng
2 $ cd /opt
3 $ sudo git clone https://github.com/munin-monitoring/contrib.git
4 $ sudo ln -s /opt/contrib/plugins/haproxy/haproxy_active_backend /etc/munin/plugins/haproxy_active_backend
5 $ sudo ln -s /opt/contrib/plugins/haproxy/haproxy_abort_backend /etc/munin/plugins/haproxy_abort_backend
6 $ sudo vi /etc/munin/plugin-conf.d/munin-node
1 [haproxy_*]
2 env.url http://odoo:odoo@localhost:1936/haproxy-status?stats;csv
3 env.backend backend_odoo_no_ssl backend_odoo_ssl
Monitor PostgreSQL
As datas resides on this server, it’s important to monitor this particular server. Depending on your configuration, some metrics are relevant or
not, like replication delay. We add postgresql server 1 in munin config then check plugins on the postgresql node.
1 ...
2 host_name odoo-pg1
3 allow ^192\.168\.56\.40$
4 ...
1 [pg;odoo-pg1]
2 address 192.168.56.21
3 [pg;odoo-pg2]
4 address 192.168.56.22
1 ...
2 postgres_autovacuum | no | yes
3 postgres_bgwriter | no | yes
4 postgres_cache_ | no | yes (+ALL +odoo)
5 postgres_checkpoints | no | yes
6 postgres_connections_ | no | yes (+ALL +odoo)
7 postgres_connections_db | no | yes
8 postgres_locks_ | no | yes (+ALL +odoo)
9 postgres_oldest_prepared_xact_ | no | no [Prepared transactions not enabled]
10 postgres_prepared_xacts_ | no | no [Prepared transactions not enabled]
11 postgres_querylength_ | no | yes (+ALL +odoo)
12 postgres_scans_ | no | yes (+odoo)
13 postgres_size_ | no | yes (+ALL +odoo)
14 ...
1 $ sudo vi /etc/munin/plugins/pg_replication
1 #!/usr/bin/env bash
2 #%# family=manual
3
4 slave=`psql postgres -A -t -c "SELECT pg_is_in_recovery();"`
5 master=`psql postgres -A -t -c "SELECT count(client_addr) from pg_stat_replication;"`
6
7 if [[ $slave == "f" ]];
8 then
9 if [[ $master -eq 0 ]];
10 then
11 exit 1
12 else
13 client=`psql postgres -A -t -c "SELECT client_addr from pg_stat_replication;"`
14 client=`dig -x $client +short`
15 fi
16 fi
17
18 case $1 in
19 config)
20 echo graph_category postgres
21 echo graph_title Streaming Replication
22 if [[ $master -gt 0 ]]; then
23 echo delay_b.label Replication Lag \($client\)\(B\)
24 echo delay_b.warning 10000000
25 echo delay_b.critical 300000000
26 fi
27 if [[ $slave == "t" ]]; then
28 echo graph_args --logarithmic --units=si
29 echo delay_s.label Replication Delay \(s\)
30 echo delay_s.warning 60
31 echo delay_s.critical 600
32 echo delay_b.label Replication Lag \(B\)
33 echo delay_b.warning 10000000
34 echo delay_b.critical 300000000
35 fi
36 exit 0
37 ;;
38 esac
39
40 if [[ $master -gt 0 ]]; then
41 # master
42 echo -n "delay_b.value "
43 psql postgres -A -t -c "select pg_xlog_location_diff(sent_location, replay_location) from pg_stat_replication;"
44 fi
45
46 if [[ $slave == "t" ]]; then
47 # slave
48 echo -n "delay_s.value "
49 psql postgres -A -t -c "
50 SELECT
51 CASE
52 WHEN pg_last_xlog_receive_location() = pg_last_xlog_replay_location() THEN 0
53 ELSE EXTRACT (EPOCH FROM now() - pg_last_xact_replay_timestamp())
54 END;"
55 echo -n "delay_b.value "
56 psql postgres -A -t -c "SELECT pg_xlog_location_diff(pg_last_xlog_receive_location(), pg_last_xlog_replay_locat
57 fi
58
59 exit 0
1 [pg_*]
2 user postgres
3 group postgres
Don’t forget ot restart munin-node on the node, and you can run munin-cron on the master as munin user to speed up a bit.
You can also run munin-run pg_replication on the node to check if everything is ok.
Disk IOs
Disk latency
CPU Usage (iowait, steal, …)
Load average
Memory usage (cache, …)
1 ...
2 host_name odoo-1
3 allow ^192\.168\.56\.40$
4 ...
1 $ sudo vi /etc/munin/munin.conf
1 [odoo;odoo-1]
2 address 192.168.56.10
3 [odoo;odoo-2]
4 address 192.168.56.11
Adjust log-level
We want to monitor response time within odoo. To achieve this, we need to get the response time from odoo, in the odoo logs. In your odoo
config file:
1 log_level = debug_rpc
We can distinguish two kind of requests. The longpoll ones, and the others.
Number of longpoll requests per minute ~= number of users, even inactive with just an open browser tab.
1 $ sudo vi /etc/munin/plugins/odootrmin
1 #!/bin/sh
2 #%# family=manual
3 #%# capabilities=autoconf suggest
4 case $1 in
5 autoconf)
6 exit 0
7 ;;
8 suggest)
9 exit 0
10 ;;
11 config)
12 echo graph_category odoo
13 echo graph_title odoo rpc request count
14 echo graph_vlabel num requests/minute in last 5 minutes
15 echo requests.label num requests
16 echo pollrequests.label num poll requests
17 exit 0
18 ;;
19 esac
20 # watch out for the time zone of the logs => using date -u for UTC timestamps
21 result=$(tail -60000 /var/log/odoo/odoo.log | grep -a "odoo.http.rpc.request" | grep -a -v "poll" | awk "BEGIN{coun
22 pollresult=$(tail -60000 /var/log/odoo/odoo.log | grep -a "odoo.http.rpc.request" | grep -a "poll" | awk "BEGIN{cou
23 echo "requests.value ${result}"
24 echo "pollrequests.value ${pollresult}"
25 exit 0
Once munin node restarted, and munin cron ran, a new graph appears.
Measure response time
It’s possible to extract the rpc response time from odoo logs. in /etc/munin/plugins/odooresponse:
1 #!/bin/sh
2 #%# family=manual
3 #%# capabilities=autoconf suggest
4 case $1 in
5 config)
6 echo graph_category odoo
7 echo graph_title odoo rpc requests min/average response time
8 echo graph_vlabel seconds
9 echo graph_args --units-exponent -3
10 echo min.label min
11 echo min.warning 1
12 echo min.critical 5
13 echo avg.label average
14 echo avg.warning 1
15 echo avg.critical 5
16 exit 0
17 ;;
18 esac
19 # watch out for the time zone of the logs => using date -u for UTC timestamps
20 result=$(tail -60000 /var/log/odoo/odoo.log | grep -a "odoo.http.rpc.request" | grep -a -v "poll" | awk "BEGIN{sum=
21 echo -n "min.value "
22 echo ${result} | cut -d" " -f1
23 echo -n "avg.value "
24 echo ${result} | cut -d" " -f2
25 exit 0
Once munin node restarted, and munin cron ran, a new graph appears.