1
0
mirror of https://github.com/linkedin/oncall.git synced 2025-11-26 23:10:47 +02:00

Set up packer/docker

This commit is contained in:
Daniel Wang
2017-05-19 18:07:35 -07:00
committed by Qingping Hou
parent 8a54b06195
commit 6a4b13a0ee
14 changed files with 552 additions and 1 deletions

View File

@@ -1,5 +1,24 @@
Oncall [![Gitter chat](https://badges.gitter.im/irisoncall/Lobby.png)](https://gitter.im/irisoncall/Lobby)
======
---
Quickstart
-------------
### For users
To set up a local instance of Oncall, we recommend using Docker. Docker is available for download at https://www.docker.com/community-edition. We have a publicly available Docker image that can be set up using just two commands. After downloading docker, use
```docker run --name oncall-mysql -e MYSQL_ROOT_PASSWORD='1234' -d mysql```
to set up a MySQL database for Oncall to run with. Note that we set the MySQL root password to '1234'; you may wish to change this to something more secure.
After setting up the mysql container, we can set up an instance of Oncall running on http://localhost:8080 with the following:
```docker run -d --link oncall-mysql:mysql -p 8080:8080 -e DOCKER_DB_BOOTSTRAP=1 quay.io/iris/oncall```
Here, we link the mysql container created in the previous step with our Oncall container, allowing for easy MySQL access between the two containers. We also pass a DOCKER_DB_BOOTSTRAP environment variable indicating to our setup script that the database needs to be initialized. This will populate the database with a small amount of dummy data and set up the proper schema so we can get up and running.
Once these two steps are complete, you can visit localhost:8080 in your browser to check out Oncall. Try logging in as the user "jdoe", with any password (the Docker image defaults to debug authentication, which authenticates all credentials so long as the user exists in the DB). You can navigate to the "Browse Teams" page and check out "Test Team", which shows a calendar page where you can create and modify events.
### For developers
<img src="https://github.com/linkedin/oncall/raw/master/src/oncall/ui/static/images/oncall_logo_blue.png" width="100">
@@ -21,6 +40,7 @@ Setup mysql schema:
```
mysql -u root -p < ./db/schema.v0.sql
mysql -u root -p < ./db/dummy_data.sql
```
Setup app config by editing configs/config.yaml.
@@ -36,6 +56,10 @@ One of the following commands:
* `make serve`
* `oncall-dev ./configs/config.yaml`
This sets up a local instance of Oncall on localhost:8080 with gunicorn. Try logging in as the user "jdoe", with any password (the Docker image defaults to debug authentication, which authenticates all credentials so long as the user exists in the DB). You can navigate to the "Browse Teams" page and check out "Test Team", which shows a calendar page where you can create and modify events.
Any changes made should be automatically picked up and displayed on refresh. Check out https://github.com/linkedin/oncall/issues for a list of outstanding issues, and tackle any one that catches your interest. Contributions are expected to be tested thoroughly and submitted with unit/end-to-end tests; look in the e2e directory for our suite of end-to-end tests.
Test
---

View File

@@ -0,0 +1,74 @@
server:
host: 0.0.0.0
port: 8080
debug: True
oncall_host: http://localhost:8080
metrics: dummy
db:
conn:
kwargs:
scheme: mysql+pymysql
user: root
password: '1234'
host: mysql
database: oncall
charset: utf8
echo: True
str: "%(scheme)s://%(user)s:%(password)s@%(host)s/%(database)s?charset=%(charset)s"
kwargs:
pool_recycle: 3600
session:
encrypt_key: 'abc'
sign_key: '123'
auth:
debug: False
module: 'oncall.auth.modules.debug'
notifier:
skipsend: True
healthcheck_path: /tmp/status
messengers:
- type: dummy
application: oncall
iris_api_key: magic
# allow_origins_list:
# - http://www.example.com
index_content_setting:
footer: |
<ul>
<li>Oncall © LinkedIn 2017</li>
<li>Feedback</li>
<li><a href="http://oncall.tools" target="_blank">About</a></li>
</ul>
notifications:
default_roles:
- "primary"
- "secondary"
- "shadow"
- "manager"
default_times:
- 86400
- 604800
default_modes:
- "email"
reminder:
activated: True
polling_interval: 360
default_timezone: 'US/Pacific'
user_validator:
activated: True
subject: 'Warning: Missing phone number in Oncall'
body: 'You are scheduled for an on-call shift in the future, but have no phone number recorded. Please update your information in Oncall.'
slack_instance: foobar
header_color: '#3a3a3a'
iris_plan_integration:
activated: True
app: oncall
api_key: magic
api_host: http://localhost:16649
plan_url: '/v0/applications/oncall/plans'

99
db/dummy_data.sql Normal file
View File

@@ -0,0 +1,99 @@
-- Sample MySQL data. Can be loaded after initializing schema
LOCK TABLES `user` WRITE;
/*!40000 ALTER TABLE `user` DISABLE KEYS */;
INSERT INTO `user` VALUES (1,'root',1,'God User',NULL,NULL,1),(2,'manager',1,'Team Admin',NULL,NULL,0),(3,'jdoe',1,'John Doe',NULL,NULL,0),(4,'asmith',1,'Alice Smith',NULL,NULL,0);
/*!40000 ALTER TABLE `user` ENABLE KEYS */;
UNLOCK TABLES;
LOCK TABLES `team` WRITE;
/*!40000 ALTER TABLE `team` DISABLE KEYS */;
INSERT INTO `team` VALUES (1,'Test Team','#team','team@example.com','US/Pacific',1,NULL);
/*!40000 ALTER TABLE `team` ENABLE KEYS */;
UNLOCK TABLES;
LOCK TABLES `service` WRITE;
/*!40000 ALTER TABLE `service` DISABLE KEYS */;
INSERT INTO `service` VALUES (1,'test service');
/*!40000 ALTER TABLE `service` ENABLE KEYS */;
UNLOCK TABLES;
LOCK TABLES `notification_setting` WRITE;
/*!40000 ALTER TABLE `notification_setting` DISABLE KEYS */;
INSERT INTO `notification_setting` VALUES (1,4,1,35,1,86400,NULL),(2,4,1,35,1,604800,NULL),(3,2,1,35,1,86400,NULL),(4,2,1,35,1,604800,NULL),(7,3,1,35,1,86400,NULL),(8,3,1,35,1,604800,NULL),(9,4,1,35,3,NULL,1);
/*!40000 ALTER TABLE `notification_setting` ENABLE KEYS */;
UNLOCK TABLES;
LOCK TABLES `roster` WRITE;
/*!40000 ALTER TABLE `roster` DISABLE KEYS */;
INSERT INTO `roster` VALUES (1,'Test Roster',1),(2,'Test Roster 2',1);
/*!40000 ALTER TABLE `roster` ENABLE KEYS */;
UNLOCK TABLES;
LOCK TABLES `roster_user` WRITE;
/*!40000 ALTER TABLE `roster_user` DISABLE KEYS */;
INSERT INTO `roster_user` VALUES (1,3,1),(1,4,1),(2,2,1);
/*!40000 ALTER TABLE `roster_user` ENABLE KEYS */;
UNLOCK TABLES;
LOCK TABLES `schedule` WRITE;
/*!40000 ALTER TABLE `schedule` DISABLE KEYS */;
INSERT INTO `schedule` VALUES (1,1,1,1,21,0,1496559600),(2,1,2,4,21,0,1496559600);
/*!40000 ALTER TABLE `schedule` ENABLE KEYS */;
UNLOCK TABLES;
LOCK TABLES `schedule_event` WRITE;
/*!40000 ALTER TABLE `schedule_event` DISABLE KEYS */;
INSERT INTO `schedule_event` VALUES (1,1,205200,604800),(2,2,205200,604800);
/*!40000 ALTER TABLE `schedule_event` ENABLE KEYS */;
UNLOCK TABLES;
LOCK TABLES `setting_role` WRITE;
/*!40000 ALTER TABLE `setting_role` DISABLE KEYS */;
INSERT INTO `setting_role` VALUES (1,1),(2,1),(3,1),(4,1),(7,1),(8,1),(9,1),(1,2),(2,2),(3,2),(4,2),(7,2),(8,2),(1,3),(2,3),(3,3),(4,3),(7,3),(8,3),(1,4),(2,4),(3,4),(4,4),(7,4),(8,4);
/*!40000 ALTER TABLE `setting_role` ENABLE KEYS */;
UNLOCK TABLES;
LOCK TABLES `team_admin` WRITE;
/*!40000 ALTER TABLE `team_admin` DISABLE KEYS */;
INSERT INTO `team_admin` VALUES (1,2),(1,4);
/*!40000 ALTER TABLE `team_admin` ENABLE KEYS */;
UNLOCK TABLES;
LOCK TABLES `team_service` WRITE;
/*!40000 ALTER TABLE `team_service` DISABLE KEYS */;
INSERT INTO `team_service` VALUES (1,1);
/*!40000 ALTER TABLE `team_service` ENABLE KEYS */;
UNLOCK TABLES;
LOCK TABLES `team_user` WRITE;
/*!40000 ALTER TABLE `team_user` DISABLE KEYS */;
INSERT INTO `team_user` VALUES (1,2),(1,3),(1,4);
/*!40000 ALTER TABLE `team_user` ENABLE KEYS */;
UNLOCK TABLES;
LOCK TABLES `user_contact` WRITE;
/*!40000 ALTER TABLE `user_contact` DISABLE KEYS */;
INSERT INTO `user_contact` VALUES (1,8,'+1 111-111-1111'),(1,17,'root'),(1,26,'+1 111-111-1111'),(1,35,'root@example.com'),(2,8,'+1 222-222-2222'),(2,17,'manager'),(2,26,'+1 222-222-2222'),(2,35,'manager@example.com'),(3,8,'+1 333-333-3333'),(3,17,'jdoe'),(3,26,'+1 333-333-3333'),(3,35,'jdoe@example.com'),(4,8,'+1 444-444-4444'),(4,17,'asmith'),(4,26,'+1 444-444-4444'),(4,35,'asmith@example.com');
/*!40000 ALTER TABLE `user_contact` ENABLE KEYS */;
UNLOCK TABLES;
LOCK TABLES `event` WRITE;
/*!40000 ALTER TABLE `event` DISABLE KEYS */;
INSERT INTO `event` VALUES (1,1,1,1,NULL,3,1495555200,1496160000),(2,1,1,1,NULL,4,1496160000,1496764800),(3,1,1,1,NULL,3,1496764800,1497369600),(7,1,4,2,NULL,2,1495555200,1496160000),(8,1,4,2,NULL,2,1496160000,1496764800),(9,1,4,2,NULL,2,1496764800,1497369600);
/*!40000 ALTER TABLE `event` ENABLE KEYS */;
UNLOCK TABLES;

View File

@@ -0,0 +1,13 @@
[Unit]
Description=nginx proxy for uwsgi-oncall
After=syslog.target
[Service]
ExecStart=/usr/sbin/nginx -c /home/oncall/daemons/nginx.conf
User=oncall
Group=oncall
Restart=on-failure
KillSignal=SIGQUIT
Type=simple
StandardError=syslog
NotifyAccess=all

View File

@@ -0,0 +1,8 @@
[Unit]
Description=Socket for oncall nginx proxy
[Socket]
ListenStream=0.0.0.0:8080
[Install]
WantedBy=sockets.target

View File

@@ -0,0 +1,13 @@
[Unit]
Description=oncall uWSGI app
After=syslog.target
[Service]
ExecStart=/bin/bash -c 'cd /home/oncall && source ./env/bin/activate && DOCKER_DB_BOOTSTRAP=1 python ./entrypoint.py'
User=oncall
Group=oncall
Restart=on-failure
KillSignal=SIGQUIT
Type=simple
StandardError=syslog
NotifyAccess=all

View File

@@ -0,0 +1,11 @@
[Unit]
Description=Socket for uWSGI app oncall
[Socket]
ListenStream=/home/oncall/var/run/uwsgi.sock
SocketUser=oncall
SocketGroup=oncall
SocketMode=0660
[Install]
WantedBy=sockets.target

64
ops/daemons/nginx.conf Normal file
View File

@@ -0,0 +1,64 @@
error_log /home/oncall/var/log/nginx/error.log;
pid /home/oncall/var/run/nginx.pid;
daemon off;
events {
worker_connections 1024;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $http_CLIENT_IP - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent $request_time '
'"$http_referer" "$http_user_agent" "$http_x_forwarded_for"';
access_log /home/oncall/var/log/nginx/access.log main;
error_log /home/oncall/var/log/nginx/error.log warn;
server {
listen 8080;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
underscores_in_headers on;
keepalive_timeout 15;
reset_timedout_connection on;
merge_slashes off;
gzip on;
gzip_proxied any;
gzip_min_length 1024;
gzip_comp_level 6;
gzip_vary on;
gzip_http_version 1.0;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
location / {
uwsgi_pass unix:///home/oncall/var/run/uwsgi.sock;
uwsgi_read_timeout 600;
include /etc/nginx/uwsgi_params ;
}
location /healthcheck {
uwsgi_pass unix:///home/oncall/var/run/uwsgi.sock;
uwsgi_read_timeout 600;
include /etc/nginx/uwsgi_params ;
access_log /home/oncall/var/log/nginx/hc_access.log main;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}

View File

@@ -0,0 +1,43 @@
prod:
plugins: python,logfile
chdir: /home/oncall/source/src
socket: /home/oncall/var/run/uwsgi.sock
chmod-socket: 666
master: True
# set the following two settings if you are running under root
# uid: 1000
# gid: 1000
workers: 12
master-fifo: /home/oncall/var/run/uwsgi_master_fifo
touch-reload: /home/oncall/var/run/uwsgi_touch_reload
stats: /home/oncall/var/run/uwsgi_stats.sock
pidfile: /home/oncall/var/run/uwsgi.pid
module: oncall.app:get_wsgi_app()
virtualenv: /home/oncall/env
pyargv: /home/oncall/config/config.yaml
buffer-size: 32768
# Enable memory reporting
memory-report: true
# Logging
logformat: '%(ltime) [%(status)] %(method) %(uri) %(addr) [%(uagent)] RT:%(msecs) REF:%(referer) SZ:%(size) %(proto)'
log-4xx: true
log-5xx: true
log-x-forwarded-for: true
log-slow: 1500
# access log
req-logger: file:/home/oncall/var/log/uwsgi/access.log
# error log
logger: file:/home/oncall/var/log/uwsgi/error.log
# put timestamp in the error log
logdate: true
# control nginx and oncall scheduler/notifier
attach-daemon2:
- cmd=/usr/sbin/nginx -c /home/oncall/daemons/nginx.conf,pidfile=/home/oncall/var/run/nginx.pid
- cmd=/home/oncall/env/bin/oncall-notifier /home/oncall/config/config.yaml
- cmd=/home/oncall/env/bin/oncall-scheduler /home/oncall/config/config.yaml

95
ops/entrypoint.py Normal file
View File

@@ -0,0 +1,95 @@
# Copyright (c) LinkedIn Corporation. All rights reserved. Licensed under the BSD-2 Clause license.
# See LICENSE in the project root for license information.
import subprocess
import os
import socket
import time
import sys
from glob import glob
from oncall.utils import read_config
dbpath = '/home/oncall/db'
initializedfile = '/home/oncall/db_initialized'
def load_sqldump(config, sqlfile, one_db=True):
print 'Importing %s...' % sqlfile
with open(sqlfile) as h:
cmd = ['/usr/bin/mysql', '-h', config['host'], '-u',
config['user'], '-p' + config['password']]
if one_db:
cmd += ['-o', config['database']]
proc = subprocess.Popen(cmd, stdin=h)
proc.communicate()
if proc.returncode == 0:
print 'DB successfully loaded ' + sqlfile
return True
else:
print ('Ran into problems during DB bootstrap. '
'oncall will likely not function correctly. '
'mysql exit code: %s for %s') % (proc.returncode, sqlfile)
return False
def wait_for_mysql(config):
print 'Checking MySQL liveness on %s...' % config['host']
db_address = (config['host'], 3306)
tries = 0
while True:
try:
sock = socket.socket()
sock.connect(db_address)
sock.close()
break
except socket.error:
if tries > 20:
print 'Waited too long for DB to come up. Bailing.'
sys.exit(1)
print 'DB not up yet. Waiting a few seconds..'
time.sleep(2)
tries += 1
continue
def initialize_mysql_schema(config):
print 'Initializing oncall database'
# disable one_db to let schema.v0.sql create the database
re = load_sqldump(config, os.path.join(dbpath, 'schema.v0.sql'), one_db=False)
if not re:
sys.exit('Failed to load schema into DB.')
for f in glob(os.path.join(dbpath, 'patches', '*.sql')):
re = load_sqldump(config, f)
if not re:
sys.exit('Failed to load DB patche: %s.' % f)
re = load_sqldump(config, os.path.join(dbpath, 'dummy_data.sql'))
if not re:
sys.stderr.write('Failed to load dummy data.')
with open(initializedfile, 'w'):
print 'Wrote %s so we don\'t bootstrap db again' % initializedfile
def main():
oncall_config = read_config(
os.environ.get('ONCALL_CFG_PATH', '/home/oncall/config/config.yaml'))
mysql_config = oncall_config['db']['conn']['kwargs']
# It often takes several seconds for MySQL to start up. oncall dies upon start
# if it can't immediately connect to MySQL, so we have to wait for it.
wait_for_mysql(mysql_config)
if 'DOCKER_DB_BOOTSTRAP' in os.environ:
if not os.path.exists(initializedfile):
initialize_mysql_schema(mysql_config)
os.execv('/usr/bin/uwsgi',
['', '--yaml', '/home/oncall/daemons/uwsgi.yaml:prod'])
if __name__ == '__main__':
main()

3
ops/packer/README.md Normal file
View File

@@ -0,0 +1,3 @@
```docker run --name oncall-mysql -e MYSQL_ROOT_PASSWORD='1234' -d mysql```
```docker run -d --link oncall-mysql:mysql -p 8080:8080 -e DOCKER_DB_BOOTSTRAP=1 quay.io/iris/oncall```

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from __future__ import print_function
import re
import os
import yaml
import json
import sys
OUTPUT_DIR = 'output'
def main():
current_dir = os.path.dirname(os.path.realpath(__file__))
with open('%s/../../src/oncall/__init__.py' % current_dir, 'r') as fd:
version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]',
fd.read(), re.MULTILINE).group(1)
print('Generating packer config for oncall v%s' % version)
yml_cfg = sys.argv[1]
with open(yml_cfg) as fp:
config = yaml.safe_load(fp)
if not os.path.isdir(OUTPUT_DIR):
os.mkdir(OUTPUT_DIR)
config['variables']['app_version'] = version
cfg_name = os.path.splitext(os.path.basename(yml_cfg))[0]
with open('%s/%s.json' % (OUTPUT_DIR, cfg_name), 'w') as fp:
json_str = json.dumps(config, indent=2)
print(json_str)
fp.write(json_str)
main()

68
ops/packer/oncall.yaml Normal file
View File

@@ -0,0 +1,68 @@
variables:
app_name: "oncall"
app_version: ""
git_tag: "master"
builders:
- type: "docker"
image: "ubuntu:16.04"
changes:
- 'EXPOSE 8080'
- 'CMD ["sudo", "-EHu", "oncall", "bash", "-c", "source /home/oncall/env/bin/activate && python /home/oncall/entrypoint.py"]'
commit: True
provisioners:
- type: "shell"
inline:
- mkdir /tmp/repo
- type: "file"
source: "../../src"
destination: "/tmp/repo"
- type: "file"
source: "../../setup.py"
destination: "/tmp/repo/setup.py"
- type: "file"
source: "../../db"
destination: "/tmp/repo"
- type: "file"
source: "../../ops"
destination: "/tmp/repo"
- type: "file"
source: "../../configs"
destination: "/tmp/repo"
- type: "shell"
only: ["docker"]
inline:
- "apt-get update && apt-get -y install sudo"
- type: "shell"
inline:
- sudo apt-get -y install curl python-pip uwsgi unzip virtualenv sudo python-dev libyaml-dev libsasl2-dev libldap2-dev nginx uwsgi-plugin-python mysql-client
- sudo rm -rf /var/cache/apt/archives/*
- sudo useradd -m -s /bin/bash oncall
- sudo chown -R oncall:oncall /home/oncall /var/log/nginx /var/lib/nginx
- sudo -Hu oncall mkdir -p /home/oncall/var/log/uwsgi /home/oncall/var/log/nginx /home/oncall/var/run
- sudo mv /tmp/repo /home/oncall/source
- "sudo chown -R oncall:oncall /home/oncall/source"
- sudo mv /home/oncall/source/ops/config/systemd/uwsgi-oncall.service /etc/systemd/system/uwsgi-oncall.service
- sudo mv /home/oncall/source/ops/config/systemd/nginx-oncall.service /etc/systemd/system/nginx-oncall.service
- sudo mv /home/oncall/source/ops/config/systemd/nginx-oncall.socket /etc/systemd/system/nginx-oncall.socket
- sudo -Hu oncall ln -s /home/oncall/source/ops/daemons /home/oncall/daemons
- sudo -Hu oncall ln -s /home/oncall/source/ops/entrypoint.py /home/oncall/entrypoint.py
- sudo -Hu oncall ln -s /home/oncall/source/db /home/oncall/db
- sudo -Hu oncall mkdir /home/oncall/config
- sudo -Hu oncall cp /home/oncall/source/configs/config.yaml /home/oncall/config/config.yaml
- sudo -Hu oncall virtualenv /home/oncall/env
- sudo -Hu oncall /bin/bash -c 'source /home/oncall/env/bin/activate && cd /home/oncall/source && pip install .'
- type: "shell"
only: ["docker"]
inline:
- "sudo -Hu oncall mv -f /home/oncall/daemons/uwsgi-docker.yaml /home/oncall/daemons/uwsgi.yaml"
- sudo -Hu oncall cp /home/oncall/source/configs/config.docker.yaml /home/oncall/config/config.yaml

View File

@@ -13,8 +13,9 @@ with open('src/oncall/__init__.py', 'r') as fd:
setup(
name='oncall',
version=version,
packages=['oncall'],
package_dir={'': 'src'},
packages=setuptools.find_packages('src'),
include_package_data=True,
install_requires=[
'falcon==1.1.0',
'falcon-cors',