前几天给自己的域名添加了子域名 git,用来访问自己搭建的 GitLab。顺便实践了一把 Docker 的应用部署。
GitLab 的外部依赖很多,有 Nginx、Rails、Postgres、Redis、MySQL、unicorn、Go 等。如果单独安装各个依赖,一大堆的配置会让人抓狂。如果用官网提供的 omni 集成包,除非是全新的服务器,否则很大可能就导致依赖的重复安装,比如进程里有多个 Nginx、MySQL,很容易把服务器环境弄得很乱。像 GitLab 这样的程序,其实很适合用 Docker 来部署,一则和实机环境隔离开,另外运行性能相当好。
安装 Docker 环境
安装配置
惯例,以 Debian 8 为参考,把 Docker 官方维护的 deb 包添加到系统的 APT 源内,创建文件 /etc/apt/sources.list.d/docker.list
:
deb https://apt.dockerproject.org/repo debian-jessie main
更新源,安装 docker-engine
包,执行 ps -ef | grep docker
查看 Docker 的进程,
root 2885 1 0 09:40 ? 00:00:10 /usr/bin/dockerd --raw-logs
root 2897 2885 0 09:40 ? 00:00:00 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --shim docker-containerd-shim --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --runtime docker-runc
sudoz 21053 6463 0 14:54 pts/0 00:00:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn docker
可以看到,Docker 的守护进程以 root 用户运行,通过绑定 Unix socket 而非 TCP 端口号来转发数据,通常 Unix socket 所属用户是 root,所以在执行 docker [command]
时需要加上 sudo
。如果非 root 用户需要 docker 命令的执行权限,可以把用户加进 docker 用户组,这样 docker 守护进程在启动时,把 Unix socket 读写权限赋予给 docker 用户组,从而使得非 root 用户获得 docker 执行权限。很简单,
sudo groupadd docker
sudo sudo usermod -aG docker $(whoami)
sudo service docker restart
基础操作
docker search [image]
在 Docker Hub 上搜索相关的镜像,并返回镜像的状态和信息;
docker pull [image]
从 Docker Hub 上下载指定的镜像,注意此时并没有运行 Docker 容器,仅仅只是下载;
docker run [image]
在容器内运行已下载的指定镜像,如果镜像未下载完成,会先执行下载;
docker ps [-l, -a]
类似系统的 ps 命令,查看当前正在运行的 Docker 容器,-l 参数是显示最近运行的容器,-a 参数显示全部的容器;
docker stop/start/restart [container]
容器有唯一的 ID,可以通过上述命令停止/启动/重启指定容器;
docker rm [container]
删除指定的容器;
docker images
查看全部已下载的 Docker 镜像;
docker rmi [image]
删除指定的 Docker 镜像;
目前为止,这些命令已经够用了。还有一些命令的参数没有展开,用到的时候再具体解释。
Docker 运行 GitLab
Docker Hub 上维护的 GitLab 镜像有好几个,我没有选官方维护的镜像,而是 sameersbn/gitlab 镜像,这个镜像相比官方的更灵活。
安装很简单,参考上面给出的 Docker 命令,先下载镜像。
docker pull sameersbn/gitlab:8.10.2
Docker 提供了一个快速运行的工具 docker-compose,docker-compose 可以将配置文件里的 docker 命令和参数解释出来并运行,镜像的作者提供了可供参考的配置文件 docker-compose.yml。从文件里可以看到,该 GitLab 镜像依赖了 Postgres 和 Redis,这些在镜像里没有包含,可以连接外部已经存在的服务,或者另起容器去运行这些依赖服务。
或者手动去运行容器,不通过 docker-compose。 首先,运行 Postgres 容器:
docker run --name gitlab-postgresql -d \
--env 'DB_NAME=gitlabhq_production' \
--env 'DB_USER=gitlab' --env 'DB_PASS=your_password' \
--env 'DB_EXTENSION=pg_trgm' \
--volume /srv/docker/gitlab/postgresql:/var/lib/postgresql \
sameersbn/postgresql:9.4-24
然后,运行 Redis 容器:
docker run --name gitlab-redis -d \
--volume /srv/docker/gitlab/redis:/var/lib/redis \
sameersbn/redis:latest
最后,运行 GitLab 容器:
docker run --name gitlab -d \
--link gitlab-postgresql:postgresql --link gitlab-redis:redisio \
--publish 10022:22 --publish 10080:80 \
--env 'GITLAB_PORT=10080' --env 'GITLAB_SSH_PORT=10022' \
--env 'GITLAB_SECRETS_DB_KEY_BASE=long-and-random-alpha-numeric-string' \
--volume /srv/docker/gitlab/gitlab:/home/git/data \
sameersbn/gitlab:8.10.2
上面的命令涉及几个参数,分别解释下:
--name
参数设置容器的名称,该名称必须唯一;-d
参数使容器在后台运行;--volume
参数设置容器挂载的磁盘目录(和宿主机共享);--env
参数设置容器的环境变量;link
参数指定容器需要连接的其他容器;publish
参数指定容器的外部端口号和内部端口号;
对比手动运行的命令参数和配置文件的参数,两者是一致的。
Docker 容器挂载的共享目录在容器删除时并不会一并被删除,这里需要留意,如果 /srv/gitlab
里的文件不需要的话,可以手动删除,以免再次运行容器时发生前后配置差异的冲突。
GitLab 容器运行起来后,通过 docker logs -f [container]
来查看运行时日志,运行正常的话,就能访问 http://localhost:10080 看到 GitLab 了。当然这只是在本地运行,如果要部署到服务器上,还需要再做些工作。我的目标是通过 git 子域名访问 Docker 容器内的 GitLab,同时支持 HTTPS 访问。
Nginx 反代 Docker 容器
要实现上面的目标,需要用到 Nginx 的反向代理,用 Nginx 作为负载的前端,将访问请求代理到 Docker 容器的外部端口上,从访问者的角度上看,就好像直接通过域名访问到 GitLab 一样。
开启 GitLab SSL 支持
Docker GitLab 的 --publish
参数设置了 10080:80 的端口号,这表明容器内部的 80 端口映射到宿主机的 10080 端口上,因此访问 Docker 容器的外部端口来访问容器内的应用。
如果是通过上述方式访问 GitLab,那么 SSL 的配置是在 Docker 内部完成,这里不做说明了,我的想法是通过外部负载,比如 Nginx,转发请求到 Docker 容器的端口上。
首先得为 GitLab 的域名准备好 SSL 证书,这个在上一篇水文中已经写了。设置 Docker 容器的环境变量 GITLAB_HTTPS=true
,使得 GitLab 支持 HTTPS,将环境变量 GITLAB_PORT
改为 443,把环境变量 GITLAB_HOST
设置为和 SSL 证书相匹配的域名,这样 GitLab 容器的运行命令变成如下:
docker run --name gitlab -d \
--publish 10022:22 --publish 10080:80 \
--env 'GITLAB_SSH_PORT=10022' --env 'GITLAB_PORT=443' \
--env 'GITLAB_HTTPS=true' --env 'GITLAB_HOST=git.isudox.com' \
--volume /srv/docker/gitlab/gitlab:/home/git/data \
sameersbn/gitlab:8.10.2
反向代理到 Docker
GitLab 容器正常启动后,还需要 Nginx 把请求反向代理到容器上。如果只是把 HTTP 的请求反代到 GitLab 并不麻烦,但需要同时把 HTTP 重定向 HTTPS。且看下面的 Nginx 配置:
upstream gitlab {
server 45.33.47.109:10080 fail_timeout=0;
}
server {
listen 80;
server_name git.isudox.com;
server_tokens off;
access_log off;
root /dev/null;
client_max_body_size 0;
location / {
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_redirect off;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Frame-Options SAMEORIGIN;
proxy_pass http://gitlab;
}
}
server {
listen 443 ssl;
server_name git.isudox.com;
server_tokens off;
root /dev/null;
client_max_body_size 0;
ssl on;
ssl_certificate /path-to-your-crt;
ssl_certificate_key /path-to-your-key;
ssl_verify_client off;
ssl_ciphers
"ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 5m;
location / {
gzip off;
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_redirect off;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Frame-Options SAMEORIGIN;
proxy_pass http://gitlab;
}
}
测试下 HTTP 到 HTTPS 的重定向,
curl -H "X-Forwarded-Proto: https" http://45.33.47.109:10080/user/sign_in
<html><body>You are being <a href="https://45.33.47.109:10080/users/sign_in">redirected</a>.</body></html>
验收下最终的成果,GitLab
最后,贴上我的 docker-composer.yml
配置,仅作备忘。
version: '2'
services:
redis:
restart: always
image: sameersbn/redis:latest
command:
- --loglevel warning
volumes:
- /srv/docker/gitlab/redis:/var/lib/redis:Z
postgresql:
restart: always
image: sameersbn/postgresql:9.4-24
volumes:
- /srv/docker/gitlab/postgresql:/var/lib/postgresql:Z
environment:
- DB_USER=gitlab
- DB_PASS=helloworld
- DB_NAME=gitlabhq_production
- DB_EXTENSION=pg_trgm
gitlab:
restart: always
image: sameersbn/gitlab:8.10.3
depends_on:
- redis
- postgresql
ports:
- "10080:80"
- "10022:22"
volumes:
- /srv/docker/gitlab/gitlab:/home/git/data:Z
environment:
- DEBUG=false
- DB_ADAPTER=postgresql
- DB_HOST=postgresql
- DB_PORT=5432
- DB_USER=gitlab
- DB_PASS=helloworld
- DB_NAME=gitlabhq_production
- REDIS_HOST=redis
- REDIS_PORT=6379
- TZ=Asia/Shanghai
- GITLAB_TIMEZONE=Beijing
- GITLAB_HTTPS=true
- SSL_SELF_SIGNED=false
- GITLAB_HOST=git.isudox.com
- GITLAB_PORT=443
- GITLAB_SSH_PORT=10022
- GITLAB_RELATIVE_URL_ROOT=
- GITLAB_SECRETS_DB_KEY_BASE=qwertyuiopasdfghjklzxcvbnm
- GITLAB_ROOT_PASSWORD=
- GITLAB_ROOT_EMAIL=
- GITLAB_NOTIFY_ON_BROKEN_BUILDS=true
- GITLAB_NOTIFY_PUSHER=false
- GITLAB_EMAIL=hi@gmail.com
- GITLAB_EMAIL_DISPLAY_NAME=GitLab
- GITLAB_EMAIL_REPLY_TO=hi@gmail.com
- GITLAB_EMAIL_ENABLED=true
- GITLAB_BACKUP_SCHEDULE=daily
- GITLAB_BACKUP_TIME=05:00
- SMTP_ENABLED=enable
- SMTP_DOMAIN=www.gmail.com
- SMTP_HOST=smtp.gmail.com
- SMTP_PORT=587
- SMTP_USER=hi@gmail.com
- SMTP_PASS=helloworld
- SMTP_STARTTLS=true
- SMTP_AUTHENTICATION=login
- IMAP_ENABLED=false
- IMAP_HOST=imap.gmail.com
- IMAP_PORT=993
- IMAP_USER=hi@gmail.com
- IMAP_PASS=helloworld
- IMAP_SSL=true
- IMAP_STARTTLS=false