The dust is settling around my development sandbox for 2016. I've got what feels like a good mix of monitoring, scalability, and cost (about $10 / month) - lots of juice for the squeeze.
Here's a guide through most of what I have set up and how you can do it yourself. My primary goal was to have an online presence that is fairly scalable with low costs. I'll talk about how to set up SSH and users, Git-based version control, deploy automation, and load balancing. I'll briefly touch on service management, deploy scripts, and reverse proxies, then we'll call it a day.
I've settled on Digital Ocean, a VPS provider with pretty good pricing and great community articles. Digital Ocean charges per hour, so it won't cost you much to play around.
This guide will use the smallest instance (droplets), weighing in at 512MB of memory and 20GB disk space, running the default Ubuntu 15.04 x64
images. I'll refer to the IP of this instance as 192.0.2.1
.
PuTTYgen is a good tool to make SSH keys in Windows (it's bundled with PuTTY). We'll make two keys: one for user root
, and one for user deploy
, who will own everything running on the commit and deploy chain.
Generate
and move the mouse for a whileKey comment
if you likePublic key for pasting into OpenSSH authorized_keys file
areakey-root.txt
file in the Documents folder for your Windows userSave private key
and save key-root.ppk
to the Documents folder (no passphrase!)key-deploy.txt
and key-deploy.ppk
On Windows, various programs (like Git) will look in your Documents folder for SSH keys, so it's good to save them there.
key-root.txt
to your Digital Ocean profile's security area.Ubuntu 15.04 x64
image and the root
SSH key.
Use PuTTY to SSH into the new droplet:
Connection -> SSH -> Auth
to add key-root.ppk
to the Private key for authentication
section.root
/var/log/auth.log
)...and create the deploy
user:
adduser --disabled-password deploy
sudo su deploy
mkdir ~/.ssh && cd ~/.ssh
authorized_keys
and open for editingkey-deploy.txt
to authorized_keys
, save and close.exit
The new user should have sudo
privileges:
visudo
and look for the User privilege specification
sectiondeploy ALL=(ALL:ALL) NOPASSWD:ALL
, save and exit.
Now, you should be able to make a new profile in PuTTY to SSH in as deploy
and run sudo
commands without requiring a password.
There's various programs to wrap the PuTTY profile tool into tabbed interfaces with various extras. My current favorite is SuperPuTTY.
Since you're running a tiny environment with limited memory available, you'll need to set up some swap space to give a little extra capacity to the memory. A good rule of thumb:
Swap should equal 2x physical RAM for up to 2 GB of physical RAM, and then an additional 1x physical RAM for any amount above 2 GB, but never less than 32 MB.
Digital Ocean has a good guide on adding swap. It's worth reading. Here's the distilled content:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo swapon -s
/etc/fstab
, add /swapfile none swap sw 0 0
/etc/sysctl.conf
, add
vm.swappiness = 20
vm.vfs_cache_pressure = 50
There's a few dependencies required for the various software installs below, so knock them out in one fell swoop:
sudo apt-get update
sudo apt-get install -y docker.io libsqlite3-dev git mysql-server vim
Note that MySQL requires a password to be set for its root user, which will be used later.
Go is required to run GOGS ("Go Git Service").
deploy
user~/.bashrc
for editingexport GOPATH=/home/deploy/go
export PATH=${PATH}:/usr/local/go/bin
source ~/.bashrc
.wget https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.5.1.linux-amd64.tar.gz
go env
I tried Gitlab earlier this year, and it was too demanding for tiny instances with limited resources. That said, it was a good experience - they have a CI runner built in and a straightforward installation process. It's a great resource for small or medium-size organizations, but not for my one man show.
GOGS is the lightweight go-to solution at the moment. It's an adolescent project, written in Go, that provides a great way to host Git version control. I cobbled together this process from a few install guides.
root
MySQL user to create the gogs
database:
mysql -u root -p -e 'DROP DATABASE IF EXISTS gogs; CREATE DATABASE IF NOT EXISTS gogs CHARACTER SET utf8 COLLATE utf8_general_ci;'
go get github.com/gogits/gogs
. It may take a while, with no notifications.
cd $GOPATH/src/github.com/gogits/gogs
go build
Again, wait a bit, and no notifications.
./gogs web
192.0.2.1:3000
If everything went well, it's now time to configure GOGS. These settings and others are stored in the custom configuration file (at /home/deploy/go/src/github.com/gogits/gogs/custom/conf/app.ini
) and can be manually edited later.
Database Settings
, update the username to root
Database Settings
, update the password for the root user.Application: General Settings
, update the Run User to deploy
Application: General Settings
, change the Application URL
to the IP of the instance (192.0.2.1)Optional Settings
At this point, you'll have to kill the GOGS process, and it becomes apparent we need a service. There are several ways to get GOGS up and running as a daemon when the system starts.
Upstart manages scripts in the /etc/init
folder, but is being "transitioned" out of Ubuntu. Upstart starts processes greedily, triggered by events, whether they are needed or not. Its replacement, Systemd, starts processes lazily, triggered by dependencies. A third option is Supervisor, which prefers the role of "service manager" rather than "init manager".
Our continuous deployer, Drone, uses either Upstart or Systemd, so we'll use them too. I'll show both here in case you prefer Ubuntu 14. (If you're interested in Supervisor, here's how to do that.)
sudo vim /lib/systemd/system/gogs.service
and add the following:[Unit]
Description=Gogs (Go Git Service)
After=syslog.target
After=network.target
[Service]
Type=simple
User=deploy
Group=deploy
WorkingDirectory=/home/deploy/go/src/github.com/gogits/gogs
ExecStart=/home/deploy/go/src/github.com/gogits/gogs/gogs web
Restart=always
Environment=USER=deploy HOME=/home/deploy
[Install]
WantedBy=multi-user.target
sudo systemctl enable gogs.service
sudo service gogs start
sudo service gogs status
sudo vim /etc/init/gogs.conf
and add the following:start on started mysql
console log
env USER=deploy
script
export HOME=/home/$USER
export GOPATH=/home/deploy/go
export PATH=/sbin:/usr/sbin:/bin:/usr/bin:$GOPATH/bin
chdir $GOPATH/src/github.com/gogits/gogs
exec ./gogs web
end script
sudo service gogs start
sudo service gogs status
sudo tail -f /var/log/upstart/gogs.log
Boom - version control. Now to add deployment automation.
Drone is a lightweight continuous deployment tool that adds Git hooks to watch your commits. It then triggers builds from a build file that is in your project. They recently released Drone 0.4
, with new docs and easier installation. It's a great project that's on the up-and-up.
Create the config file at /etc/drone/dronerc
, and add the following configuration:
REMOTE_DRIVER=gogs
REMOTE_CONFIG=http://192.0.2.1:3000
DATABASE_DRIVER=sqlite3
DATABASE_CONFIG=/var/lib/drone/drone.sqlite
Pull the Docker image: docker pull drone/drone:0.4
, and fire up the Docker container:
sudo docker run
--volume /var/lib/drone:/var/lib/drone
--volume /var/run/docker.sock:/var/run/docker.sock
--env-file /etc/drone/dronerc
--restart=always
--publish=4000:8000
--detach=true
--name=drone
drone/drone:0.4
Note that in the installation docs, Drone is published to :80
by default. Our load balancer will be listening on that port for all incoming traffic, so Drone has to be served from a different port - we'll use :4000
.
You should now be able navigate to 192.0.2.1:4000
and see something like this:
You can now log in with your GOGS account. Drone will automatically synchronize publically available repos, and you can activate the ones you wish to watch. They're triggered by deployment scripts.
You may be thinking, "If both Drone and GOGS have support for both MySQL and SQLite, why don't we configure them to use the same database software, and get a performance increase?" The answer, for now, is surprisingly clear.
go build -tags "sqlite"
. The dependencies introduced here can't run on a tiny instance.
Configuration files are popular at the moment in continuous deployment solutions for good reason:
If you've used Jenkins, you know that anything in the above list takes some doing to accomplish. Tools like Gitlab CI and Travis CI use configs written in YAML. I've found it to be a great approach.
Drone offers a variety of ways to work with its Docker container when the build task runs. A very basic configuration looks like this:
build:
image: golang
commands:
- echo hello from drone.yml
- whoami
- ./your-deploy-script.sh
Once this is run, you'll see your commands being executed in the build output. The last line runs a shell script you can create in the root of your project. Similar to Jenkins, I've found it's easiest to have a shell script to run, mostly because they support variables to hold server IPs or repository URIs.
FROM ubuntu
RUN apt-get -y --install-recommends --install-suggests update
RUN apt-get -y --install-recommends install git
Here's a few helpful commands to help work with Docker:
# Use a Dockerfile to build an image with a (t)ag.
sudo docker build -t IMAGENAME /absolute/path/to/dockerfile
# Run Bash that is (i)nteractive and outputs to (t)erminal
sudo docker run -it IMAGENAME /bin/bash
# Output report of all images, filter for untagged, and for each one, remove.
sudo docker rmi $(docker images | grep "^<none>")
# Output report of (a)ll containers (IDs only using the -q flag) and for each one, remove.
sudo docker rm $(docker ps -aq)
This example assumes you have a domain name ("YOURDOMAIN.COM") and it's registered to your droplet IP. You should be able to reach GOGS on YOURDOMAIN.COM:3000
.
sudo apt-get install haproxy
/etc/default/haproxy
, set ENABLED=1
mv /etc/haproxy/haproxy.cfg{,.original}
/etc/haproxy/haproxy.cfg
and enter the following:
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
frontend http-in
mode http
bind *:80
acl gogs_request hdr(host) gogs.YOUR_DOMAIN.COM
use_backend gogs if gogs_request
default_backend YOUR_SERVER_POOL
backend gogs
http-request set-header X-Forwarded-Port %[dst_port]
server gogs YOURDOMAIN.COM:3000 check
backend YOUR_SERVER_POOL
mode http
balance roundrobin
#option forwardfor
http-request set-header X-Forwarded-Port %[dst_port]
option httpchk HEAD / HTTP/1.1\r\nHost:localhost
server YOUR_SERVER_01 192.0.2.1:80 check
server YOUR_SERVER_02 192.0.2.2:80 check
Basically, HAProxy listens for HTTP requests on :80
, checks if they match an (a)ccess (c)ontrol (l)ist, and routes to a specific backend or a default.
The config is fairly readable; any unclear commands are explained pretty well in the documentation. You can see that HAProxy works as a reverse proxy server, distributing requests to backends. You can experiment and make addresses for Drone, and the stats monitoring page (search for "Monitoring HAProxy") served by HAProxy.
Most important, you can now add as many servers as you want and the traffic will be balanced across them. Go forth and scale!