-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
1,155 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
We welcome contributions! | ||
|
||
### Guidelines | ||
|
||
* Small and focused PRs. Please don't include changes that don't address the subject of your PR. | ||
* Follow the style of importing functions directly e.g. `from os.path import abspath`. | ||
* Check out the [core values of Piku](../README.md#core-values). | ||
* PEP8. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
# Design Notes | ||
|
||
The idea behind `piku` is that it provides the simplest possible way to deploy web apps or services. Simplicity comes at the expense of features, of course, and this document tries to capture the trade-offs. | ||
|
||
## Why uWSGI | ||
|
||
Using [uWSGI][uwsgi] in [emperor mode][emperor] gives us the following features for free: | ||
|
||
* Painless Python WSGI and `virtualenv` integration | ||
* Process monitoring, restarting, basic resource limiting, etc. | ||
* Basic security scaffolding, beginning with the ability to define `uid`/`gid` on a per-app basis (if necessary) | ||
|
||
## Application packaging | ||
|
||
An app is simply a `git` repository with some additional files on the top level, the most important of which is the `Procfile`. | ||
|
||
### `Procfile` format | ||
|
||
`piku` recognizes six kinds of process declarations in the `Procfile`: | ||
|
||
* `wsgi` workers, in the format `dotted.module:entry_point` (Python-only) | ||
* `web` workers, which can be anything that honors the `PORT` environment variable | ||
* `static` workers, which simply mount the first argument as the root static path | ||
* `preflight` which is a special "worker" that is run once _before_ the app is deployed _and_ installing deps (can be useful for cleanups). | ||
* `release` which is a special worker that is run once when the app is deployed, after installing deps (can be useful for build steps). | ||
* `cron` workers, which require a simplified `cron` expression preceding the command to be run (e.g. `cron: */5 * * * * python batch.py` to run a batch every 5 minutes) | ||
* `worker` processes, which are standalone workers and can have arbitrary names | ||
|
||
So a Python application could have a `Procfile` like such: | ||
|
||
```bash | ||
# A module to be loaded by uwsgi to serve HTTP requests | ||
wsgi: module.submodule:app | ||
# A background worker | ||
worker: python long_running_script.py | ||
# Another worker with a different name | ||
fetcher: python fetcher.py | ||
# Simple cron expression: minute [0-59], hour [0-23], day [0-31], month [1-12], weekday [1-7] (starting Monday, no ranges allowed on any field) | ||
cron: 0 0 * * * python midnight_cleanup.py | ||
release: python initial_cleanup.py | ||
``` | ||
|
||
...whereas a generic app would be: | ||
|
||
```bash | ||
web: embedded_server --port $PORT | ||
worker: background_worker | ||
``` | ||
|
||
Any worker will be automatically respawned upon failure ([uWSGI][uwsgi] will automatically shun/throttle crashy workers). | ||
|
||
## `ENV` settings | ||
|
||
Since `piku` is targeted at [12 Factor apps][12f], it allows you to set environment variables in a number of ways, the simplest of which is by adding an `ENV` file to your repository: | ||
|
||
```bash | ||
SETTING1=foo | ||
# piku supports comments and variable expansion | ||
SETTING2=${SETTING1}/bar | ||
# if this isn't defined, piku will assign a random TCP port | ||
PORT=9080 | ||
``` | ||
|
||
See [ENV.md](./ENV.md) for a full list of Piku variables which can also be set. | ||
|
||
Environment variables can be changed after deployment using `config:set`. | ||
|
||
## Runtime detection | ||
|
||
`piku` follows a very simple set of rules to determine what kind of runtime is required: | ||
|
||
1. If there's a `requirements.txt` file at the top level, then the app is assumed to require Python. | ||
2. _TODO: Go_ | ||
3. _TODO: Node_ | ||
4. _TODO: Java_ | ||
2. For all the rest, a `Procfile` is required to determine application entry points. | ||
|
||
|
||
## Application isolation | ||
|
||
Application isolation can be tackled at several levels, the most relevant of which being: | ||
|
||
* OS/process isolation | ||
* Runtime/library isolation | ||
|
||
For 1.0, all applications run under the same `uid`, under separate branches of the same filesystem, and without any resource limiting. | ||
|
||
Ways to improve upon that (short of full containerisation) typically entail the use of a `chroot` jail environment (which is available under most POSIX systems in one form or another) or Linux kernel namespaces - both of which are supported by [uWSGI][uwsgi] (which can also handle resource limiting to a degree). | ||
|
||
As to runtime isolation, `piku` only provides `virtualenv` support until 1.0. Python apps can run under Python 2 or 3 depending on the setting of `PYTHON_VERSION`, but will always use pre-installed interpreters (Go, Node and Java support will share these limitations in each major version). | ||
|
||
## Internals | ||
|
||
`piku` uses two `git` repositories for each app: a bare repository for client push, and a clone for deployment (which is efficient in terms of storage since `git` tries to use hardlinks on local clones whenever possible). | ||
|
||
This separation makes it easier to cope with long/large deployments and restore apps to a pristine condition, since the app will only go live after the deployment clone is reset (via `git checkout -f`). | ||
|
||
## Components | ||
|
||
This diagram (available as a `dot` file in the `img` folder) outlines how its components interact: | ||
|
||
![](../img/piku.png) | ||
|
||
[uwsgi]: https://github.com/unbit/uwsgi | ||
[emperor]: http://uwsgi-docs.readthedocs.org/en/latest/Emperor.html | ||
[12f]: http://12factor.net |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# Configuring Piku via ENV | ||
|
||
You can configure deployment settings by placing special variables in an `ENV` file deployed with your app. | ||
|
||
## Runtime Settings | ||
|
||
* `PIKU_AUTO_RESTART` (boolean, defaults to `true`): Piku will restart all workers every time the app is deployed. You can set it to `0`/`false` if you prefer to deploy first and then restart your workers separately. | ||
|
||
### Python | ||
|
||
* `PYTHON_VERSION` (int): Forces Python 3 | ||
|
||
### Node | ||
|
||
* `NODE_VERSION`: installs a particular version of node for your app if `nodeenv` is found on the path. Optional; if not specified, the system-wide node package is used. | ||
|
||
> **NOTE**: you will need to stop and re-deploy the app to change the `node` version in a running app. | ||
## Network Settings | ||
|
||
* `BIND_ADDRESS`: IP address to which your app will bind (typically `127.0.0.1`) | ||
* `PORT`: TCP port for your app to listen in (if deploying your own web listener). | ||
* `DISABLE_IPV6` (boolean): if set to `true`, it will remove IPv6-specific items from the `nginx` config, which will accept only IPv4 connections | ||
|
||
## uWSGI Settings | ||
|
||
* `UWSGI_MAX_REQUESTS` (integer): set the `max-requests` option to determine how many requests a worker will receive before it's recycled. | ||
* `UWSGI_LISTEN` (integer): set the `listen` queue size. | ||
* `UWSGI_PROCESSES` (integer): set the `processes` count. | ||
* `UWSGI_ENABLE_THREADS` (boolean): set the `enable-threads` option. | ||
* `UWSGI_LOG_MAXSIZE` (integer): set the `log-maxsize`. | ||
* `UWSGI_LOG_X_FORWARDED_FOR` (boolean): set the `log-x-forwarded-for` option. | ||
* `UWSGI_GEVENT`: enable the Python 2 `gevent` plugin | ||
* `UWSGI_ASYNCIO` (integer): enable the Python 2/3 `asyncio` plugin and set the number of tasks | ||
* `UWSGI_INCLUDE_FILE`: a uwsgi config file in the app's dir to include - useful for including custom uwsgi directives. | ||
* `UWSGI_IDLE` (integer): set the `cheap`, `idle` and `die-on-idle` options to have workers spawned on demand and killed after _n_ seconds of inactivity. | ||
|
||
> **NOTE:** `UWSGI_IDLE` applies to _all_ the workers, so if you have `UWSGI_PROCESSES` set to 4, they will all be killed simultaneously. Support for progressive scaling of workers via `cheaper` and similar uWSGI configurations will be added in the future. | ||
## `nginx` Settings | ||
|
||
* `NGINX_SERVER_NAME`: set the virtual host name associated with your app | ||
* `NGINX_STATIC_PATHS` (string, comma separated list): set an array of `/url:path` values that will be served directly by `nginx` | ||
* `NGINX_CLOUDFLARE_ACL` (boolean, defaults to `false`): activate an ACL allowing access only from Cloudflare IPs | ||
* `NGINX_HTTPS_ONLY` (boolean, defaults to `false`): tell `nginx` to auto-redirect non-SSL traffic to SSL site. | ||
|
||
> **NOTE:** if used with Cloudflare, `NGINX_HTTPS_ONLY` will cause an infinite redirect loop - keep it set to `false`, use `NGINX_CLOUDFLARE_ACL` instead and add a Cloudflare Page Rule to "Always Use HTTPS" for your server (use `domain.name/*` to match all URLs). | ||
### `nginx` Caching | ||
|
||
When `NGINX_CACHE_PREFIXES` is set, `nginx` will cache requests for those URL prefixes to the running application (`uwsgi`-like or `web` workers) and reply on its own for `NGINX_CACHE_TIME` to the outside. This is meant to be used for compute-intensive operations like resizing images or providing large chunks of data that change infrequently (like a sitemap). | ||
|
||
The behavior of the cache can be controlled with the following variables: | ||
|
||
* `NGINX_CACHE_PREFIXES` (string, comma separated list): set an array of `/url` values that will be cached by `nginx` | ||
* `NGINX_CACHE_SIZE` (integer, defaults to 1): set the maximum size of the `nginx` cache, in GB | ||
* `NGINX_CACHE_TIME` (integer, defaults to 3600): set the amount of time (in seconds) that valid backend replies (`200 304`) will be cached. | ||
* `NGINX_CACHE_REDIRECTS` (integer, defaults to 3600): set the amount of time (in seconds) that backend redirects (`301 307`) will be cached. | ||
* `NGINX_CACHE_ANY` (integer, defaults to 3600): set the amount of time (in seconds) that any other replies (other than errors) will be cached. | ||
* `NGINX_CACHE_CONTROL` (integer, defaults to 3600): set the amount of time (in seconds) for cache control headers (`Cache-Control "public, max-age=3600"`) | ||
* `NGINX_CACHE_EXPIRY` (integer, defaults to 86400): set the amount of time (in seconds) that cache entries will be kept on disk. | ||
* `NGINX_CACHE_PATH` (string, detaults to `~piku/.piku/<appname>/cache`): location for the `nginx` cache data. | ||
|
||
> **NOTE:** `NGINX_CACHE_PATH` will be _completely managed by `nginx` and cannot be removed by Piku when the application is destroyed_. This is because `nginx` sets the ownership for the cache to be exclusive to itself, and the `piku` user cannot remove that file tree. So you will either need to clean it up manually after destroying the app or store it in a temporary filesystem (or set the `piku` user to the same UID as `www-data`, which is not recommended). | ||
Right now, there is no provision for cache revalidation (i.e., `nginx` asking your backend if the cache entries are still valid), since that requires active application logic that varies depending on the runtime--`nginx` will only ask your backend for new content when `NGINX_CACHE_TIME` elapses. If you require that kind of behavior, that is still possible via `NGINX_INCLUDE_FILE`. | ||
|
||
Also, keep in mind that using `nginx` caching with a `static` website worker will _not_ work (and there's no point to it either). | ||
|
||
### `nginx` Overrides | ||
|
||
* `NGINX_INCLUDE_FILE`: a file in the app's dir to include in nginx config `server` section - useful for including custom `nginx` directives. | ||
* `NGINX_ALLOW_GIT_FOLDERS`: (boolean) allow access to `.git` folders (default: false, blocked) | ||
|
||
## Acme Settings | ||
|
||
* `ACME_ROOT_CA`: set the certificate authority that Acme should use to generate public ssl certificates (string, default: `letsencrypt.org`) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# FAQ | ||
|
||
**Q:** Why `piku`? | ||
|
||
**A:** Partly because it's supposed to run on a [Pi][pi], because it's Japanese onomatopeia for 'twitch' or 'jolt', and because I know the name will annoy some of my friends. | ||
|
||
**Q:** Why Python/why not Go? | ||
|
||
**A:** I actually thought about doing this in Go right off the bat, but [click][click] is so cool and I needed to have [uWSGI][uwsgi] running anyway, so I caved in. But I'm very likely to take something like [suture](https://github.com/thejerf/suture) and port this across, doing away with [uWSGI][uwsgi] altogether. | ||
|
||
Go also (at the time) did not have a way to vendor dependencies that I was comfortable with, and that is also why Go support fell behind. Hopefully that will change soon. | ||
|
||
**Q:** Does it run under Python 3? | ||
|
||
**A:** Right now, it _only_ runs on Python 3, even though it can deploy apps written in both major versions. It began its development using 2.7 and using`click` for abstracting the simpler stuff, and I eventually switched over to 3.5 once it was supported in Debian Stretch and Raspbian since I wanted to make installing it on the Raspberry Pi as simple as possible. | ||
|
||
**Q:** Why not just use `dokku`? | ||
|
||
**A:** I used `dokku` daily for most of my personal stuff for a good while. But it relied on a number of `x64` containers that needed to be completely rebuilt for ARM, and when I decided I needed something like this (March 2016) that was barely possible - `docker` itself was not fully baked for ARM yet, and people were at the time trying to get `herokuish` and `buildstep` to build on ARM. | ||
|
||
|
||
|
||
[click]: http://click.pocoo.org | ||
[pi]: http://www.raspberrypi.org | ||
[dokku]: https://github.com/dokku/dokku | ||
[raspi-cluster]: https://github.com/rcarmo/raspi-cluster | ||
[cygwin]: http://www.cygwin.com | ||
[uwsgi]: https://github.com/unbit/uwsgi | ||
[wsl]: https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# Installation on CentOS 9 | ||
|
||
> This is a standalone, distribution-specific version of `INSTALL.md`. You do not need to read or follow the original file, but can refer to it for generic steps like setting up SSH keys (which are assumed to be common knowledge here) | ||
All steps done as root (or add sudo if you prefer). | ||
|
||
## Dependencies | ||
|
||
Before installing `piku`, you need to install the following packages: | ||
|
||
```bash | ||
dnf in -y ansible-core ansible-collection-ansible-posix ansible-collection-ansible-utils nginx nodejs npm openssl postgresql postgresql-server postgresql-contrib python3 python3-pip uwsgi uwsgi-logger-file uwsgi-logger-systemd | ||
pip install click | ||
``` | ||
|
||
## Set up the `piku` user | ||
|
||
```bash | ||
adduser --groups nginx piku | ||
# copy & setup piku.py | ||
su - piku -c "wget https://raw.githubusercontent.com/piku/piku/master/piku.py && python3 ~/piku.py setup" | ||
``` | ||
|
||
## Set up SSH access | ||
|
||
See INSTALL.md | ||
|
||
## uWSGI Configuration | ||
|
||
[FYI The uWSGI Emperor – multi-app deployment](https://uwsgi-docs.readthedocs.io/en/latest/Emperor.html) | ||
|
||
```bash | ||
mv /home/piku/.piku/uwsgi/uwsgi.ini /etc/uwsgi.d/piku.ini # linking alone increases the host attack service if one can get inside the piku user or one of its apps, so moving is safer | ||
chown piku:piku /etc/uwsgi.d/piku.ini # In Tyrant mode (set by default in /etc/uwsgi.ini) the Emperor will run the vassal using the UID/GID of the vassal configuration file | ||
systemctl restart uwsgi | ||
journalctl -feu uwsgi # see logs | ||
``` | ||
|
||
## `nginx` Configuration | ||
|
||
[FYI Setting up and configuring NGINX](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/deploying_web_servers_and_reverse_proxies/setting-up-and-configuring-nginx_deploying-web-servers-and-reverse-proxies) | ||
|
||
```bash | ||
echo "include /home/piku/.piku/nginx/*.conf;" > /etc/nginx/conf.d/piku.conf | ||
systemctl restart nginx | ||
journalctl -feu nginx # see logs | ||
``` | ||
|
||
## Set up systemd.path to reload nginx upon config changes | ||
|
||
```bash | ||
# Set up systemd.path to reload nginx upon config changes | ||
su - | ||
git clone https://github.com/piku/piku.git # need a copy of some files | ||
cp -v piku/piku-nginx.{path,service} /etc/systemd/system/ | ||
systemctl enable piku-nginx.{path,service} | ||
systemctl start piku-nginx.path | ||
# Check the status of piku-nginx.service | ||
systemctl status piku-nginx.path # should return `active: active (waiting)` | ||
``` | ||
|
||
## Notes | ||
|
||
|
||
|
||
[uwsgi]: https://github.com/unbit/uwsgi |
Oops, something went wrong.