作者归档:173ops

GitLab搭建与维护(基于docker镜像sameersbn/docker-gitlab)

tag: git, gitlab, subversion, sameersbn/docker-gitlab

1. 阅读本文基础

  • 熟悉git使用
  • 熟悉docker

2. GitLab简介

2.1. 概述

  • GitLab 是一个用于仓库管理系统的开源项目。使用Git作为代码管理工具,并在此基础上搭建起来的web服务。Github是公共的git仓库,而Gitlab适合于搭建企业内部私有git仓库
  • 官网:
    https://about.gitlab.com/
    https://github.com/gitlabhq/gitlabhq
    
  • GIT与SVN的比较:
    http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000/001374027586935cf69c53637d8458c9aec27dd546a6cd6000
    http://www.oschina.net/news/12542/git-and-svn
    
  • 截止本文创建时间,GitLab最新版本为v7.4.3

2.2. 架构

  • 示意图:
  • gitlab_arch

2.3. 组件

  • 前端:Nginx,用于页面及Git tool走http或https协议
  • 后端:Gitlab服务,采用Ruby on Rails框架,通过unicorn实现后台服务及多进程
  • SSHD:开启sshd服务,用于用户上传ssh key进行版本克隆及上传。注:用户上传的ssh key是保存到git账户中
  • 数据库:目前仅支持MySQL和PostgreSQL
  • Redis:用于存储用户session和任务,任务包括新建仓库、发送邮件等等
  • Sidekiq:Rails框架自带的,订阅redis中的任务并执行

3. GitLab安装部署

3.1. 官方支持的方式

3.2. 采用docker镜像安装GitLab

3.2.1. 简介

3.2.2. 架构图

docker_gitlab_arch

3.2.3. 下载镜像

  • docker pull sameersbn/gitlab:7.4.3 # 下载gitlab镜像
  • docker pull sameersbn/mysql:latest # 下载gitlab所用到的mysql镜像
  • docker pull sameersbn/redis:latest # 下载gitlab所用到的redis镜像

3.2.4. 安装

3.2.4.1. 启动redis

  • 命令:
    docker run \
    	--name=gitlab_redis \
    	-tid \
    	sameersbn/redis:latest
    

3.2.4.2. 启动mysql

  • mkdir -p /opt/gitlab/mysql
  • 命令:
    docker run \
    	--name=gitlab_mysql \
    	-tid \
    	-e 'DB_NAME=gitlabhq_production' \
    	-e 'DB_USER=gitlab' \
    	-e 'DB_PASS=password' \
    	-v /opt/gitlab/mysql:/var/lib/mysql \
    	sameersbn/mysql:latest
    

3.2.4.3. 启动gitlab

  • mkdir -p /opt/gitlab/data /opt/gitlab/log
  • 命令:
    docker run \
    	--name='gitlab' \
    	-itd \
    	--link gitlab_mysql:mysql \
    	--link gitlab_redis:redisio \
    	-e 'GITLAB_PORT=80' \
    	-e 'GITLAB_SSH_PORT=22' \
    	-e 'GITLAB_HOST=gitlab.example.com' \
    	-v /var/run/docker.sock:/run/docker.sock \
    	-v $(which docker):/bin/docker \
    	-v /opt/gitlab/data:/home/git/data \
    	-v /opt/gitlab/log:/var/log/gitlab \
    	sameersbn/gitlab:7.4.3
    
    
    上述是开启一个基本gitlab。
    完整(包含LDAP、EMAIL):
    docker run \
    	--name='gitlab' \
    	-itd \
    	--link gitlab_mysql:mysql \
    	--link gitlab_redis:redisio \
    	-e 'GITLAB_PORT=80' \
    	-e 'GITLAB_SSH_PORT=22' \
    	-e 'LDAP_ENABLED=true' \
    	-e 'LDAP_HOST=192.168.1.1' \
    	-e 'LDAP_PORT=389' \
    	-e 'LDAP_UID=sAMAccountName' \
    	-e 'LDAP_METHOD=plain' \
    	-e 'LDAP_BIND_DN=test@example.com' \
    	-e 'LDAP_PASS=passwd' \
    	-e 'LDAP_BASE=OU=example_users,DC=example-family,DC=com' \
    	-e 'LDAP_ACTIVE_DIRECTORY=true' \
    	-e 'LDAP_ALLOW_USERNAME_OR_EMAIL_LOGIN=false' \
    	-e 'GITLAB_HOST=gitlab.example.com' \
    	-e 'SMTP_ENABLED=true' \
    	-e 'SMTP_DOMAIN=example.com' \
    	-e 'SMTP_HOST=192.168.1.2' \
    	-e 'SMTP_PORT=25' \
    	-e 'SMTP_STARTTLS=false' \
    	-v /var/run/docker.sock:/run/docker.sock \
    	-v $(which docker):/bin/docker \
    	-v /opt/gitlab/data:/home/git/data \
    	-v /opt/gitlab/log:/var/log/gitlab \
    	sameersbn/gitlab:7.4.3
    

这一步骤会耗时几分钟,因为这一步会做一些初始化操作,例如导入数据表结构等。可以通过docker logs gitlab来查看安装过程。同理,mysql、redis容器也可以通过docker logs gitlab_mysql和docker logs gitlab_redis来查看启动信息。

注意:上面创建的3个容器必须位于同一台宿主上,因为–link gitlab_mysql:mysql –link gitlab_redis:redisio就是将同个宿主上的容器做链接

  • 当然,redis和mysql也支持使用ip+端口,不用–link,带上环境变量即可,方法是:
    完整:
    docker run \
    	--name='gitlab' \
    	-itd \
    	--net=none \
    	--hostname='gitlab.example.com' \
    	-e 'DB_TYPE=mysql' \
    	-e 'DB_HOST=192.168.3.1' \
    	-e 'DB_PORT=3356' \
    	-e 'DB_NAME=gitlabhq_production' \
    	-e 'DB_USER=gitlab' \
    	-e 'DB_PASS=passwd' \
    	-e 'REDIS_HOST=192.168.3.2' \
    	-e 'REDIS_PORT=6402' \
    	-e 'UNICORN_WORKERS=20' \
    	-e 'GITLAB_PORT=80' \
    	-e 'GITLAB_SSH_PORT=22' \
    	-e 'LDAP_ENABLED=true' \
    	-e 'LDAP_HOST=192.168.3.3' \
    	-e 'LDAP_PORT=389' \
    	-e 'LDAP_UID=sAMAccountName' \
    	-e 'LDAP_METHOD=plain' \
    	-e 'LDAP_BIND_DN=test@example.com' \
    	-e 'LDAP_PASS=passwd' \
    	-e 'LDAP_BASE=OU=example_users,DC=example-family,DC=com' \
    	-e 'LDAP_ACTIVE_DIRECTORY=true' \
    	-e 'LDAP_ALLOW_USERNAME_OR_EMAIL_LOGIN=false' \
    	-e 'GITLAB_HOST=gitlab.example.com' \
    	-e 'SMTP_ENABLED=true' \
    	-e 'SMTP_DOMAIN=example.com' \
    	-e 'SMTP_HOST=192.168.3.4' \
    	-e 'SMTP_PORT=25' \
    	-e 'SMTP_STARTTLS=false' \
    	-v /var/run/docker.sock:/run/docker.sock \
    	-v $(which docker):/bin/docker \
    	-v /opt/gitlab/data:/home/git/data \
    	-v /opt/gitlab/log:/var/log/gitlab \
    	sameersbn/gitlab:7.4.3
    
    sameersbn/gitlab:7.4.3不支持redis的任何验证,只能无密码使用
    

目前发现sameersbn/gitlab:7.4.3有一个非常坑的地方:容器启动可能连不上数据库,是因为容器启动时会探测数据库是否可用,而探测的方法是mysqladmin连接DB_HOST的3306端口,而不是DB_PORT指定的端口,已提交issue给作者。

3.2.4.4. 给gitlab容器配置IP

  • pipework br1 gitlab 192.168.1.1/24@192.168.1.254

在gitlab容器启动(docker run)时可以加上-p参数来将容器里的端口映射到宿主上,但个人比较倾向给容器配置一个独立IP,因此上述命令没有采用-p来做端口映射,但是-e ‘GITLAB_PORT=80’ -e ‘GITLAB_SSH_PORT=22’ -e ‘GITLAB_HOST=gitlab.example.com’依然要指定,否则gitlab无法使用

3.2.4.5. 安装已完成,可以打开页面

  • url: gitlab.example.com
    账户:root
    密码:5iveL!fe
    
  • 效果:gitlab_example

3.2.4.6. 加入开机启动

  • 加入/etc/rc.local
    echo 'docker start gitlab_redis' >> /etc/rc.local
    echo 'docker start gitlab_mysql' >> /etc/rc.local
    echo 'docker start gitlab' >> /etc/rc.local
    echo 'pipework br1 gitlab 192.168.1.1/24@192.168.1.254' >> /etc/rc.local
    

4. GitLab API wrappers

  • 官方推荐:https://about.gitlab.com/applications/
  • python:pyapi-gitlab
    基本使用:
    pip install pyapi-gitlab
    import gitlab
    git = gitlab.Gitlab("http://gitlab.example.com", token="EHBLkwhr_WYzn-sXNnNs")		# token即在页面上Profile settings->Account里能看到自动生成好的Private token
    git.getusers()
    
  • 顺便推荐个Git本身的python api模块:https://github.com/FriendCode/gittle

其他很多git库的实现都太底层了,和linux的git命令完全不一样,一点也不友好。但目前发现这个模块上传下载代码只支持SSH,不支持HTTP,因此我还是更喜欢使用linux git命令

5. GitLab使用的FAQ

5.1. git代码发布支持2种方法:ssh和http(s)

  • ssh:必须添加ssh key才能发布
  • http:使用用户名、密码,若接入了LDAP,就是LDAP中的账户密码
  • https:暂未测试过

5.2. 限制git上传的单个文件大小

  • 若gitlab是通过sameersbn/gitlab:7.4.3镜像创建的,那么可以通过环境变量NGINX_MAX_UPLOAD_SIZE进行限制

5.3. 通过API进行一些操作例如创建Project,得到成功的响应,但却没有立即生效,过了几秒才生效

  • 由于GitLab为异步架构,Ruby on Rails收到创建Project请求后将该任务推送到Redis中,但是没有等待执行完毕,而是直接返回成功。后台Sidekiq从Redis订阅任务并实时执行,但由于执行需要时间,而API返回很快,因此会造成这种情况发生。目前没有其他解决办法,只能在自己程序逻辑里sleep几秒进行重试

5.4. 页面上”Profile settings->SSH Keys”与”Project Settings里Deploy Keys”的区别

  • Profile settings->SSH Keys:是用户全局的Key,具有对该用户所有项目仓库进行上传下载(push、clone)的权限
  • Project Settings里Deploy Keys:针对某个项目,具有下载(clone)的权限,而不具备上传(push)的权限

5.5. gitlab默认时区是UTC

  • sameersbn/gitlab:7.4.3镜像无法对时区进行修改,若是手动安装的gitlab,可以通过修改一个.rb文件来生效,具体没有测试过,有需要的可以自行百度搜下。

时区是UTC主要影响的是数据库里关于时间字段的值(created_at、updated_at等),对用户git仓库是不影响的,因为git仓库的创建、代码更新都是用户通过git工具自行操作,因此是用户自己的时区。

6. GitLab维护

6.1. 数据路径、日志路径

  • 由于gitlab容器在启动时已将宿主/opt/gitlab/data、/opt/gitlab/log目录挂载到容器里,因此可以在宿主上进入这2个目录中查看,另外可以通过docker logs查看。

6.2. 高可用

  • 官方有篇关于GitLab高可用的文章:https://about.gitlab.com/high-availability/
    有1/3都在讲述高可用的利弊以及不同程度的高可用带来的收益及可能引发的更多问题,说的挺有道理的。由于笔者也才刚开始使用GitLab,因此暂不测试多机load balance或failover。
    
  • 2种备份恢复方式
    1. 备份配置、仓库、数据库。
    2. 给文件系统做快照。
    官方认为第二种方法比起第一种更快,因为可以省去人为介入restore的麻烦
    XFS文件系统支持快照
    
    有2篇文章可以拜读下:
    http://www.icicletech.com/blog/gitlab-backup-made-easy 数据库数据备份到本地
    http://doc.gitlab.com/ce/raketasks/backup_restore.html 官方的,和上述其实一样
    
  • 我的备份方案:
    一. 简介:每天固定时间备份数据(仅备份MySQL和GitLab仓库重要文件,其他不备份)。若数据丢失,需要恢复,则将最近备份的数据用来恢复,会将所有数据,包括本地文件(仓库等文件)、数据库一同还原回备份时刻。
    
    二. 具体操作:
    	1. 备份:
    docker run \
    	--name='gitlab_backup' \
    	-it \
    	--rm \
    	--link gitlab_mysql:mysql \
    	--link gitlab_redis:redisio \
    	-v /var/run/docker.sock:/run/docker.sock \
    	-v $(which docker):/bin/docker \
    	-v /opt/gitlab/data:/home/git/data \
    	-v /opt/gitlab/log:/var/log/gitlab \
    	sameersbn/gitlab:7.4.3 app:rake gitlab:backup:create
    
    	   过程能在屏幕上看到,备份会自动打成tar包放入/opt/gitlab/data/backups里。可以自行对该tar包做压缩,例如gzip 时间戳_gitlab_backup.tar。
    	   若$?=0表示备份成功,然后将生成的文件使用gzip压缩为.tar.gz,然后rsync推到存储上
           通过rsync命令传到一台备份存储上
    	2. 恢复:
    docker run \
    	--name='gitlab_restore' \
    	-it \
    	--rm \
    	--link gitlab_mysql:mysql \
    	--link gitlab_redis:redisio \
    	-v /var/run/docker.sock:/run/docker.sock \
    	-v $(which docker):/bin/docker \
    	-v /opt/gitlab/data:/home/git/data \
    	-v /opt/gitlab/log:/var/log/gitlab \
    	sameersbn/gitlab:7.4.3 app:rake gitlab:backup:restore
    
           屏幕上会将/opt/gitlab/data/backups中的所有文件和目录列出来,复制粘贴要恢复的tar包文件名,敲入回车即开始恢复。
    	   注意:恢复时会将当前数据库中的所有表先删掉再导入备份tar包的里sql文件,因此此步要小心。
    	   
    	   若redis、mysql是使用环境变量带入gitlab容器的,备份和恢复命令也类似,将启动gitlab的命令复制过来,修改下--name,添加一个--rm,CMD改为gitlab:backup:create或gitlab:backup:restore即可
    
    三. 注意:
        1. 用户配置的key(即data/.ssh)是保存在数据库里,在gitlab服务启动时候会从数据库里加载并导入到.ssh里,被gitlab-shell管理
        2. 由于ssh目录(保存着服务器sshd服务启动时自己生成的ssh_key)不备份,因此还原时候若这个目录里有文件,则不去重新生成ssh_key,若不存在,则重新生成。若重新生成,用户使用ssh协议进行git push,会报错告知不合法的认证
        3. 备份只会备份3个目录:repositories、db、uploads,然后会额外生成一个backup_information.yml文件
        4. gitlab-satellites和tmp都是临时目录,因此不参与备份
        5. docker run --rm不会影响容器退出状态的输出
        6. 还原时候会将数据库里的表先DROP表再创建,因为sql文件在INSERT之前会drop table if exists
        7. 还原时候,若存在要恢复的目录,则会将原来的目录mv成.old.时间戳

[MySQL FAQ]系列 — 从MyISAM转到InnoDB需要注意什么

问题
当前,绝大多数业务场景用InnoDB已经完全能搞定了,越来越多的业务从MyISAM转向InnoDB引擎,那么有哪些注意事项呢?

分析
当了解完两种引擎的不同之处,很轻松的就能知道有哪些关键点了。
总的来说,从MyISAM转向InnoDB的注意事项有:

1、MyISAM的主键索引中,可以在非第一列(非第一个字段)使用自增列,而InnoDB的主键索引中包含自增列时,必须在最前面;这个特性在discuz论坛中,被设计用于“抢楼”功能,因此,若有类似的业务,则无法将该表从MyISAM转成InnoDB,需要自行变通实现(我们则是将其改到Redis中实现);
2、不带条件频繁统计全表总记录数时(SELECT COUNT(*) FROM TAB),InnoDB相对较慢,而MyISAM则飞快;不过,如果是基于索引条件的统计,则二者相差不大;
3、InnoDB在5.6以前不支持全文索引,不过这个相信无所谓,没什么人会在MySQL里直接跑全文索引,尤其是对中文的全文索引(前阵子有开发同学提需求直接被我否了),确实有需要的话,可以采用Sphinx、Lucene等其他方案实现;
4、一次性导入大量数据并且后续还要进行加工处理的,可以先导入到MyISAM引擎表中,经过一通加工处理完后,再导入InnoDB表(我曾经在业务中用此方法提高数据批量导入及处理效率);
5、InnoDB不支持LOAD TABLE FROM MASTER语法(不过应该也很少人使用吧);
从MyISAM转成InnoDB可以享受的好处则有:

1、完整事务特性支持,以及更高的数据并发存取效率,即更高的TPS;
2、数据库实例异常重启后,InnoDB表能自动修复,而且速度相对更快,而MyISAM需要被触发才能修复,且相对耗时可能多4~5倍甚至更多;
3、更高的数据读取性能,因为InnoDB把数据及索引同时缓存在内存中,而MyISAM只缓存了索引;
4、InnoDB支持外键(不过在MySQL中,应该很少人用到外键);
两个引擎间的重要区别详情见下:

MyISAM引擎的特点:
1、堆组织表;
2、不支持事务;
3、数据文件和索引文件分开存储;
4、支持全文索引;
5、主键索引和二级索引完全一样都是B+树的数据结构,只有是否唯一的区别(主键和唯一索引有唯一属性,其他普通索引没有唯一属性。B+树叶子节点存储的都是指向行记录的row pointer);
6、有特殊计数器记录当前记录数;
7、不支持Crash recovery;
8、索引文件很容易损坏;
InnoDB引擎的特点

1、索引组织表;
2、支持事务;
3、数据文件和索引文件存储在同一个表空间中;
4、在5.6以前,不支持全文索引;
5、主键和二级索引数据结构一样都是B+树,但叶子节点存储的键值不一样(主键的叶子节点存储整行数据,因此也称为聚集索引;而二级索引的叶子节点存储的是主键的键值)
5、支持Crash recovery;
6、相同数据量时,InnoDB表空间文件大小约为MyISAM引擎的1.5~2倍;
关于InnoDB、MyISAM两种引擎的对比测试,可以参考Percona的这个对比:http://www.percona.com/blog/2007/01/08/innodb-vs-myisam-vs-falcon-benchmarks-part-1/

[MySQL FAQ]系列 — 如何将两个表名对调

问题
有位同学问我,在类似pt-osc场景下,需要将两个表名对调,怎么才能确保万无一失呢?

分析
估计其他同学就笑了,表名对掉还不简单吗,相互RENAME一下嘛。

但是,我们想要的是同时完成表名对调,如果是先后的对掉,可能会导致有些数据写入失败,那怎么办?

其实也不难,从MySQL手册里就能找到方法,那就是:同时锁定2个表,不允许写入,然后对调表名。

我们通常只锁一个表,那么同时锁两个表应该怎么做呢,可以用下面的方法:

LOCK TABLES t1 WRITE, t2 WRITE;
ALTER TABLE t1 RENAME TO t3;
ALTER TABLE t2 RENAME TO t1;
ALTER TABLE t3 RENAME TO t2;
UNLOCK TABLES;
看到了吧,其实很简单,两个表同时加表级写锁,然后用ALTER语法改名就可以了。

废话挺多的,谢谢各位客官耐心看完 :)

[MySQL FAQ]系列 — 修改my.cnf配置不生效

问题
修改了 my.cnf 配置文件后,却不生效,这是怎么回事?
原因
我们注意到,这里只说了修改 my.cnf,并没有说清楚其绝对路径是哪个文件。也就是说,有可能修改的不是正确路径下的my.cnf文件。

 

在MySQL中,是允许存在多个 my.cnf 配置文件的,有的能对整个系统环境产生影响,例如:/etc/my.cnf。有的则只能影响个别用户,例如:~/.my.cnf。

MySQL读取各个my.cnf配置文件的先后顺序是:

  • /etc/my.cnf
  • /etc/mysql/my.cnf
  • /usr/local/mysql/etc/my.cnf
  • ~/.my.cnf
  • 其他自定义路径下的my.cnf,例如:/data/mysql/yejr_3306/my.cnf

不管是mysqld服务器端程序,还是mysql客户端程序,都可以采用下面两个参数来自行指定要读取的配置文件路径:

  • –defaults-file=#, 只读取指定的文件(不再读取其他配置文件)
  • –defaults-extra-file=#, 从其他优先级更高的配置文件中读取全局配置后,再读取指定的配置文件(有些选项可以覆盖掉全局配置从的设定值)

因此,可以看到,如果你修改的是非“著名”目录下的 my.cnf,有可能看起来是不生效的,需要自行指定,或者统一放在 /etc/my.cnf 下,采用多实例的方式来管理即可。

福建技术嘉年华:20141018技术沙龙活动PPT

本次活动的PPT已经上传到百度云盘上,请点击这里 查看!
本次活动的视频已经上传到百度云盘上,请点击这里 查看!

下次活动预告:
时间:2015年1月
地点:待定(预计在软件园,可容纳100多人)
主要内容:系统运维、数据库、运维开发、大数据、移动开发、测试、安全等领域,我们也非常欢迎其他同行可以主动报名提供主题分享!
敬请关注!

Docker基础与高级

  1. Docker安装
  2. devicemapper
  3. 玩转net namespace
  4. port map
  5. 直接使用docker默认分配的IP对外提供服务(测试中)
  6. Docker COMMAND
  7. 搭建私有Registry注册中心
  8. docker with HTTPS
  9. Docker Web-UI(shipyard)
  10. 镜像制作
  11. 内置bridge(nat)和自建网络桥接使用区别
  12. Docker Event事件监听
  13. 神器
  14. FAQ

tag: cloud, virtual, docker, lxc

1. Docker安装

  • 按照官方说明:红帽6、centos均通过epel源,执行yum install docker-io进行docker安装,启动服务是service docker start
  • 经测试,红帽6、centos也可以通过在官网上下载编译好的二进制文件到本地也可以使用,但需要提前手动执行service cgconfig start来挂载cgroup,然后./docker-latest -d来启动服务。
    下载地址:https://get.docker.io/builds/Linux/x86_64/docker-latest
    https://get.docker.io/builds/Linux/x86_64/docker-1.0.1
    

但是官方提示需要内核大于3.8版本,否则可能会有问题。el>3.8

2. devicemapper

  • 扩容存储池大小、扩容容器文件系统大小
    https://www.dockboard.org/resizing-docker-containers-with-the-device-mapper-plugin/
    
    实验成功,但是一旦容器关闭再启动,就会报错,还得根据文档再做一次dmsetup load; dmsetup resume才能成功启动容器(但是如果不先启动容器,就无法使用dmsetup命令来resume),因此能否用于生产环境有待继续探索
    

3. 玩转net namespace

  • 首先要支持ip netns指令。而红帽6及epel均不支持,解决方法:
    yum install -y http://rdo.fedorapeople.org/rdo-release.rpm
    yum update -y iproute
    
  • ip netns
    直接执行这个命令(或ip netns list)读取的是/var/run/netns下的文件名,因此若不存在/var/run/netns,需要mkdir -p /var/run/netns
    
  • 配置像LXC一样的网络
    I. 宿主配置
      1. 宿主上升级iproute包,以便支持ip netns指令:
        yum install -y http://rdo.fedorapeople.org/rdo-release.rpm
        yum update -y iproute
    
      2. 在宿主上配置好桥接:
        一. 方法1(不推荐): 敲命令配置桥接(很容易导致网络中断,需要ILO连上操作)
          1) 创建桥接网卡br1并激活:brctl addbr br1; ip link set br1 up
          2) 配置br1的mac地址,和宿主准备桥接的网卡mac相同,通常为内网网卡eth1:ip link set br1 address xx:xx:xx:xx:xx:xx
          3) 给br1配置一个ip地址,或者将eth1的ip地址配置在br1上,2种方法任选其一都可行:
           前者:
           ifconfig br1 192.168.2.1 netmask 255.255.255.0
           后者:
           ifconfig eth1 0.0.0.0; ifconfig br1 192.168.2.2 netmask 255.255.255.0
          4) 配置宿主网关,从br1出
           ip ro del default
           ip ro add default via 192.168.2.254 dev br1
          5) 将eth1桥接至br1:
           brctl addif br1 eth1
        二. 方法2(推荐):写网卡配置文件
          ifcfg-br1:
    		DEVICE="br1"
    		TYPE="Bridge"
    		NM_CONTROLLED="no"
    		ONBOOT="yes"
    		BOOTPROTO="static"
    		IPADDR=192.168.2.2
    		NETMASK=255.255.255.0
    
          ifcfg-eth1:
    		DEVICE="eth1"
    		BRIDGE="br1"
    		BOOTPROTO="none"
    		NM_CONTROLLED="no"
    		ONBOOT="yes"
    		TYPE="Ethernet"
    
          注意:要在/etc/sysconfig/network-scripts/ifup-eth里if [ "${TYPE}" = "Bridge" ]; then -> fi段落最后(fi前)加个ip link set br1 address $(get_hwaddr eth1),防止桥接网卡mac地址随机生成导致网络暂时中断
    
        service network restart		# 重启网络生效
    
    II. 容器配置:
      1. 启动docker容器:
           docker run -t -i -d --name="net_test" --net=none centos:latest /bin/bash
           记录下输出(即CONTAINER ID),然后通过docker inspect -f '{{.State.Pid}}' CONTAINER ID获得该容器的pid(也即容器首进程在宿主上的pid),假设为1000
      2. 为容器创建网卡命名空间,建立点对点连接(容器命名空间网卡和宿主上生成的网卡点对点)
           mkdir -p /var/run/netns		#创建网络命名空间目录,ip netns会读取该目录下的文件名
           ln -s /proc/1000/ns/net /var/run/netns/1000		#将网络命名空间文件软链接到/var/run/netns,以便ip netns能够读取
           ip link add vethA type veth peer name vethB		#在宿主上创建2张直连网卡(vethA与vethB直连),将vethA作为容器里的网卡,vethB作为宿主上能看到的网卡
           ip link set vethB up			# 激活网卡vethB
           ip link set vethA netns 1000		# 将刚才创建的网卡归位网络命名空间
           配置vethA网卡参数:
             ip netns exec 1000 ip link set vethA name eth1
             ip netns exec 1000 ip addr add 192.168.2.3/24 dev eth1
             ip netns exec 1000 ip link set eth1 up
             ip netns exec 1000 ip route add default via 192.168.2.254 dev eth1
           brctl addif br1 vethB			# 将eth1桥接至br1
      3. 测试:
          docker attach登录容器,查看是否能ping通网关及其他子网或公网
    

3.1. ENV(环境变量)

  • Dockerfile支持ENV参数,表示启动容器时候设置变量。

只在CMD启动的进程export设置变量,而不是将变量赋值命令写入/etc/profile等脚本里,因此通过ssh方式登录容器获得的shell是没有这个变量的

4. port map

  • docker支持端口映射,通过iptables DNAT将宿主上的端口转发至容器ip对应端口。

虽然配置了端口映射后,在宿主上通过netstat -lntpu可以看到docker进程会监听这个端口,但是还没发现其作用,因为流量直接从iptables就进入容器里。

  • docker run -p、docker run -P、docker port作用:
    docker run -P 就是将image定好的port给做个端口映射(若没指定-p,则外部端口随机)
    docker run -p "8080:80" 启动容器时候做端口映射:宿主的0.0.0.0:8080 -> 容器80
    docker run -P -p "8080:80" 假如image已经有一个port 22的配置,那么就会做2个端口映射:宿主0.0.0.0:xxxxx -> 容器22、宿主0.0.0.0:8080 -> 容器80
    docker port 是查看容器已经做了端口映射的端口被映射到了哪个端口上,其实直接用docker ps就能看到,使用docker port可能是为了方便二次开发
    

5. 直接使用docker默认分配的IP对外提供服务(测试中)

5.1. 使用参数以及将docker0的ip配为机房内网网段

  • 将宿主eth1桥接到docker0上,将docker0的ip更改为原来eth1的ip(机房内网网段)

存在一个问题:docker run时候分配的ip是从1开始,到254。因此存在和网关或者其他机器ip冲突的可能,无法避免。因此docker分配ip不会做ping检查是否存活

docker run分配出去的ip,docker kill并且docker rm都不会收回并重新使用,而是重启docker daemon才会将ip收回

  • –iptables=false
    使用这个参数后,就不会再往iptables里生成nat、forward等信息了。
    
    这样启动的容器,登录容器能看到网关是宿主docker0的ip,这样网络是通的,是可以访问外网,但路是这么走的:
    1. 容器里的数据包将数据经过point-to-point网卡传送到宿主的对应veth网卡上
    2. 宿主veth网卡接收到数据包后发现网关是254,于是通过icmp数据包告知网关是254,然后容器发送数据包时自动将网关更改为254,可以从ping的输出看到:
    [ 17:37:23-root@21e77bf38fc0:~ ]#ping www.baidu.com
    PING www.a.shifen.com (115.239.210.27) 56(84) bytes of data.
    64 bytes from 115.239.210.27: icmp_seq=1 ttl=55 time=13.9 ms
    From 192.168.3.1: icmp_seq=2 Redirect Host(New nexthop: 192.168.3.254)
    64 bytes from 115.239.210.27: icmp_seq=2 ttl=55 time=13.6 ms
    From 192.168.3.1: icmp_seq=3 Redirect Host(New nexthop: 192.168.3.254)
    64 bytes from 115.239.210.27: icmp_seq=3 ttl=55 time=13.6 ms
    From 192.168.3.1: icmp_seq=4 Redirect Host(New nexthop: 192.168.3.254)
    64 bytes from 115.239.210.27: icmp_seq=4 ttl=55 time=14.0 ms
    From 192.168.3.1: icmp_seq=5 Redirect Host(New nexthop: 192.168.3.254)
    64 bytes from 115.239.210.27: icmp_seq=5 ttl=55 time=14.7 ms
    From 192.168.3.1: icmp_seq=6 Redirect Host(New nexthop: 192.168.3.254)
    64 bytes from 115.239.210.27: icmp_seq=6 ttl=55 time=13.8 ms
    64 bytes from 115.239.210.27: icmp_seq=7 ttl=55 time=13.7 ms
    From192.168.3.1: icmp_seq=8 Redirect Host(New nexthop: 192.168.3.254)
    64 bytes from 115.239.210.27: icmp_seq=8 ttl=55 time=13.8 ms
    64 bytes from 115.239.210.27: icmp_seq=9 ttl=55 time=13.6 ms
    64 bytes from 115.239.210.27: icmp_seq=10 ttl=55 time=13.5 ms
    From 192.168.3.1: icmp_seq=11 Redirect Host(New nexthop: 192.168.3.254)
    64 bytes from 115.239.210.27: icmp_seq=11 ttl=55 time=13.8 ms
    64 bytes from 115.239.210.27: icmp_seq=12 ttl=55 time=14.1 ms
    64 bytes from 115.239.210.27: icmp_seq=13 ttl=55 time=13.8 ms
    64 bytes from 115.239.210.27: icmp_seq=14 ttl=55 time=13.6 ms
    64 bytes from 115.239.210.27: icmp_seq=15 ttl=55 time=13.7 ms
    64 bytes from 115.239.210.27: icmp_seq=16 ttl=55 time=13.8 ms
    From 192.168.3.1: icmp_seq=17 Redirect Host(New nexthop: 192.168.3.254)
    64 bytes from 115.239.210.27: icmp_seq=17 ttl=55 time=13.6 ms
    64 bytes from 115.239.210.27: icmp_seq=18 ttl=55 time=13.8 ms
    64 bytes from 115.239.210.27: icmp_seq=19 ttl=55 time=13.7 ms
    64 bytes from 115.239.210.27: icmp_seq=20 ttl=55 time=14.1 ms
    64 bytes from 115.239.210.27: icmp_seq=21 ttl=55 time=13.7 ms
    64 bytes from 115.239.210.27: icmp_seq=22 ttl=55 time=13.8 ms
    64 bytes from 115.239.210.27: icmp_seq=23 ttl=55 time=13.7 ms
    64 bytes from 115.239.210.27: icmp_seq=24 ttl=55 time=13.9 ms
    64 bytes from 115.239.210.27: icmp_seq=25 ttl=55 time=14.2 ms
    64 bytes from 115.239.210.27: icmp_seq=26 ttl=55 time=13.7 ms
    64 bytes from 115.239.210.27: icmp_seq=27 ttl=55 time=13.7 ms
    64 bytes from 115.239.210.27: icmp_seq=28 ttl=55 time=13.8 ms
    64 bytes from 115.239.210.27: icmp_seq=29 ttl=55 time=13.9 ms
    64 bytes from 115.239.210.27: icmp_seq=30 ttl=55 time=14.1 ms
    64 bytes from 115.239.210.27: icmp_seq=31 ttl=55 time=13.9 ms
    64 bytes from 115.239.210.27: icmp_seq=32 ttl=55 time=13.9 ms
    64 bytes from 115.239.210.27: icmp_seq=33 ttl=55 time=14.0 ms
    64 bytes from 115.239.210.27: icmp_seq=34 ttl=55 time=13.7 ms
    64 bytes from 115.239.210.27: icmp_seq=35 ttl=55 time=14.0 ms
    64 bytes from 115.239.210.27: icmp_seq=36 ttl=55 time=14.4 ms
    64 bytes from 115.239.210.27: icmp_seq=37 ttl=55 time=13.9 ms
    

docker服务启动时候会把内核参数ip.forward给打开(数据包转发)

6. Docker COMMAND

6.1. docker参数

  • –api-enable-cors
    开启cors,以便浏览器能够通过ajax调用。但是若开启了tls,使用cors就变得困难了,目前网络上还未找到解决方案
    

6.2. run

  • –link:2个容器互通
    其实就做3件事:
    1. 若有端口映射,则在iptables的FORWARD链里将端口ACCEPT
    2. /etc/hosts:做link的容器的/etc/hosts能看到被link的容器的hosts条目
    3. 环境变量:做link的容器可以看到被link的容器的环境变量(仅为--env变量),如:ALIAS_ENV_变量名、ALIAS_NAME=xxx
    
  • –volume: 目录共享
    支持2种模式:
    1. 从宿主挂载:-v /tmp:/tmp/foo 表示将宿主的/tmp目录挂载至容器的/tmp/foo目录,可读可写,和mount --bind的效果类似
    2. 容器之间共享:
      启动第一个容器时带参数-v /tmp/foo表示在宿主上创建/var/lib/docker/vfs/dir/xxxxx(id,但不是容器id),然后挂进容器的/tmp/foo目录;
      启动第二个容器时带参数--volumes-from=b5f8320cf019(*第一个容器id)表示和第一个容器共享挂载,因此第二个容器启动后也能从df -h看到/tmp/foo目录被挂载。从inspect也可以很容易看出来(2个容器的inspect以下内容相同):
        "Volumes": {
            "/opt": "/var/lib/docker/vfs/dir/a7b1b03773d9391718b8524e7ac001bb877eb6d0596fa2a4328435d8c49f2415"
        },
        "VolumesRW": {
            "/opt": true
        }
    

7. 搭建私有Registry注册中心

7.1. 下载软件

  • 安装pip:
    yum install python-devel libevent-devel python-pip gcc xz-devel
    
  • 安装registry:
    pip install docker-registry
    pip install docker-registry[BUGSNAG]
    pip install -i http://pypi.douban.com/simple/ backports.lzma
    pip install --upgrade -i http://pypi.douban.com/simple/ backports.lzma
    

7.2. 启动服务

  • 使用默认配置文件启动服务:
    cd /usr/lib/python2.6/site-packages/config
    cp config_sample.yml config.yml
    mkdir /tmp/registry                 # config_sample.yml的默认配置
    gunicorn --access-logfile - --debug -k gevent -b 0.0.0.0:5000 -w 1 docker_registry.wsgi:application
    
  • 加入开机启动
    可以将以下内容写入/etc/rc.local,或者放在一个脚本里,/etc/rc.local调用这个脚本
    pkill gunicorn
    sleep 1
    rm -f /data/docker-registry.db
    sleep 1
    /usr/bin/gunicorn --access-logfile /opt/logs/docker-registry/access.log --error-logfile /opt/logs/docker-registry/error.log --daemon --debug -k gevent -b 0.0.0.0:5000 -w 8 docker_registry.wsgi:application
    sleep 3
    pkill gunicorn
    sleep 1
    /usr/bin/gunicorn --access-logfile /opt/logs/docker-registry/access.log --error-logfile /opt/logs/docker-registry/error.log --daemon --debug -k gevent -b 0.0.0.0:5000 -w 8 docker_registry.wsgi:application
    
  • 启动不成功FAQ
    1. 日志目录没有创建
    2. .db文件已存在,启动前删掉就行
    排错可通过error.log分析
    

7.3. 使用

  • 上传镜像
    docker tag busybox localhost:5000/busybox
    docker push localhost:5000/busybox
    
    上传成功后在/tmp/registry上应该能看到2个目录:images和repositories
    
  • 下载镜像
    docker pull localhost:5000/busybox
    
    若想给下载的镜像取个别名:
    docker tag localhost:5000/busybox aliasname 或 docker tag id aliasname  #id为localhost:5000/busybox的image id
    
    删除别名:
    docker rmi aliasname
    当然也可以选择把原始名字删掉:
    docker rmi localhost:5000/busybox
    

若一个镜像有至少2个tag,那么通过docker rmi删除的只是别名,若只有1个名字(没有别名),那么删除的是真正的镜像。

已测试:当镜像还在上传过程中时,其他机器是无法pull下来的。因此不会生成不完整的镜像

  • 搜索镜像
    curl --silent "http://192.168.1.1:5000/v1/search?q="  | json_reformat
    

7.4. 套一层透明代理(不推荐,有bug)

  • 配置nginx,vhost内容如下
    upstream docker-registry {
          server 127.0.0.1:5000;
    }
    
    server {
          listen 192.168.1.1:80;
          server_name registry.17173ops.com;
    
          proxy_set_header Host       $host;
          proxy_set_header X-Real-IP  $remote_addr;
          
          client_max_body_size 0; # disable any limits to avoid HTTP 413 for large image uploads
    
          # required to avoid HTTP 411: see Issue #1486 (https://github.com/dotcloud/docker/issues/1486)
          chunked_transfer_encoding on;
          
          location / {
                proxy_pass http://docker-registry;
          }
    }
    
  • 注意:只有以下2种情况,套一层透明代理是行的通的:
    1. nginx监听80端口,gunicorn监听0.0.0.0
    2. nginx监听非80端口,且gunicorn监听在网络可通的ip,如192.168.1.1这样的内网ip,而不能监听在127.0.0.1
    
    测试发现,当端口不为80时,在docker push时候,会首先连接image name中的ip、端口,如registry.17173ops.com:80,然后连接实际的gunicorn端口。因此当gunicorn端口无法访问时,就会报错。
    而端口为80时候就不会这样,原因未知,应该是代码逻辑。
    <=1.1.2版本都有如上共性,>1.1.2的尚未测试。
    

7.5. Web UI

  • 下载
    docker pull atcol/docker-registry-ui
    
  • 启动
    docker run --name "registry_UI" -tid -p 127.0.0.1:5001:8080 -e REG1=http://registry.17173ops.com/v1/ atcol/docker-registry-ui:latest
    
  • 开机启动:
    echo 'docker start registry_UI' >> /etc/rc.local
    
  • 套一层透明代理
    配置nginx,vhost内容如下
    upstream docker-registry-web {
          server 127.0.0.1:5001;
    }
    
    server {
          listen 192.168.1.1:80;
          server_name registry-web.17173ops.com;
    
          location / {
                proxy_pass http://docker-registry-web;
          }
    }
    
  • 使用
    http://registry-web.17173ops.com/
    
  • 注意:页面上的删除镜像只是删除tag标签,实际id和image不会删除
    docker镜像的元数据里记录着FROM哪个镜像,如果真的删除镜像所有数据,正常逻辑应该是A->B B->C -C->D D->E,这样一连串都给删除了,但是其中的某个镜像可能又被其他镜像给依赖了,猜测正是基于这种逻辑,才只删除了tag
    

8. docker with HTTPS

8.1. 原理

  • Docker HTTPS原理:双向验证。官方说明(https://docs.docker.com/articles/https/):
    In daemon mode, it will only allow connections from clients authenticated by a certificate signed by that CA. 
    In client mode, it will only connect to servers with a certificate signed by that CA.
    核心:服务端和客户端的数字证书都由同一个CA签发,因此双方在认证通讯时使用和签发时的同一个CA就能互相认证。
    

8.2. 使用

8.2.1. 创建CA(证书颁发中心)

  • 测试
    echo 01 > ca.srl
    openssl genrsa -des3 -out ca-key.pem 2048
    openssl req -new -x509 -days 3650 -key ca-key.pem -out ca.pem
    

8.2.2. 创建服务端公钥和私钥

  • 生成私钥:
    openssl genrsa -des3 -out server-key.pem 2048
    
  • 生成公钥(数字签名证书):
    1. 生成CSR文件(Certificate Signing Request 证书签名请求):openssl req -subj '/CN=docker.17173ops.com' -new -key server-key.pem -out server.csr
    2. 编写openssl.conf文件,内容如下:
    ------------- BEGIN ---------------------------------
    [req]
    distinguished_name = req_distinguished_name
    req_extensions = v3_req
    
    [req_distinguished_name]
    
    [ v3_req ]
    subjectAltName = @alt_names
    
    [alt_names]
    DNS.1 = *.h.173ops.com
    DNS.2 = *.docker.17173ops.com
    ------------- END ------------------------------------
    
  • 生成公钥(数字签名证书):
    openssl x509 -req -days 3650 -in server.csr -CA ca.pem -CAkey ca-key.pem -out server-cert.pem -extensions v3_req -extfile openssl.conf
    

8.2.3. 创建客户端公钥和私钥

  • 生成私钥:
    openssl genrsa -des3 -out client-key.pem 2048
    
  • 生成公钥(数字签名证书):
    1. 生成CSR文件(Certificate Signing Request 证书签名请求):openssl req -subj '/CN=client' -new -key client-key.pem -out client.csr
    2. echo extendedKeyUsage = clientAuth > extfile.cnf
    3. 生成公钥(数字签名证书):openssl x509 -req -days 3650 -in client.csr -CA ca.pem -CAkey ca-key.pem -out client-cert.pem -extfile extfile.cnf
    

8.2.4. 移除服务端私钥、客户端私钥密码

  • 服务端
    openssl rsa -in server-key.pem -out server-key.pem
    
  • 客户端
    openssl rsa -in client-key.pem -out client-key.pem
    

8.2.5. 使用

  • 将服务端3个文件拷贝到docker daemon(假设为192.168.1.2)的/root/.docker/下:
    1. scp ca.pem server-cert.pem server-key.pem 192.168.1.2:/root/.docker/
    2. 登录192.168.1.2
       cd /root/.docker/
       chmod 0600 *
    3. 添加启动参数:
       修改/etc/sysconfig/docker中的other_args值,添加以下内容,如:other_args="--graph /opt/docker --tlsverify --tlscacert=/root/.docker/ca.pem --tlscert=/root/.docker/server-cert.pem --tlskey=/root/.docker/server-key.pem -H unix:///var/run/docker.sock -H tcp://192.168.1.2:2376 -H tcp://127.0.0.1:2376"
    
  • 将客户端3个文件拷贝至”中控机”,然后就可以通过HTTPS远程对docker daemon进行操作:
    docker --tlsverify --tlscacert=/opt/docker_tls/ca.pem --tlscert=/opt/docker_tls/client-cert.pem --tlskey=/opt/docker_tls/client-key.pem -H localhost:2376 images
    

8.2.6. 管理

  • 查看CSR文件:
    openssl req -noout -text -in server.csr
    
  • 查看签名证书(server-cert.pem、client-cert.pem):
    openssl x509 -noout -text -in server-cert.pem
    

9. Docker Web-UI(shipyard)

9.1. 工作原理

  • 在每台docker宿主机上启动一个容器(shipyard/agent),这个容器通过挂载宿主的/var/run/docker.sock文件来获取该宿主上容器、镜像的信息,同时在容器启动时将agent注册到管理中心(shipyard/deploy)上,实现从Web查看和操作docker容器与镜像
  • 每个docker宿主启动一个agent(shipyard/agent),管理中心启动一个server(shipyard/deploy)

9.2. server配置

9.2.1. 下载镜像

  • docker pull shipyard/deploy

9.2.2. 启动容器(自动完成部署)

  • docker run -i -t -v /var/run/docker.sock:/docker.sock shipyard/deploy setup
    执行该命令时,实际做了如下操作:
    在本地宿主上依次下载并启动
    1. shipyard/redis
    2. shipyard/router
    3. shipyard/lb(Load Balance的意思)
    4. shipyard/db
    5. shipyard/shipyard(Web-UI)
    
    如何做到上述的:docker run -i -t -v /var/run/docker.sock:/docker.sock shipyard/deploy setup会将socket文件挂载进容器里,这样在容器里就能对宿主的镜像和容器进行管理,而shipyard/deploy的CMD是一个脚本,这个脚本就是进行上述操作
    

9.2.3. 验证

  • docker ps查看是否都已启动

注意docker logs shipyard_router可能会看到大量报错,并且占用大量CPU,负载也会升高。暂时不知原因,已提交至github.com,等待答复,详见https://github.com/shipyard/docker-shipyard-router/issues/3

9.3. agent配置

9.3.1. 下载镜像

  • docker pull shipyard/agent

9.3.2. 启动容器(自动注册到server)

  • docker run -i -t -v /var/run/docker.sock:/docker.sock -e IP=`ip -4 address show br1 | grep ‘inet ‘ | sed ‘s/.*inet \([0-9\.]\+\).*/\1/’` -e URL=http://192.168.1.1:8000 -p 4500:4500 shipyard/agent
    如果看不懂,可以这么写:
    docker run -i -t -v /var/run/docker.sock:/docker.sock -e IP=192.168.1.2 -e URL=http://192.168.1.1:8000 -p 4500:4500 shipyard/agent
    (192.168.1.2是docker宿主ip)
    
    注意:要先启动server,才能启动agent,否则agent注册到server可能会失败。
    

9.4. 页面配置

9.4.1. 登录页面

9.4.2. 接受agent注册

9.5. 注意

9.5.1. 页面上的Images(http://192.168.1.1:8000/images/)进行镜像删除要注意

  • 自动进行了去重(根据image id),因此从页面上删除镜像时会将相同image id的全部删除

9.5.2. server端管理

  • 移除:docker run -i -t -v /var/run/docker.sock:/docker.sock shipyard/deploy cleanup
  • 重启:docker run -i -t -v /var/run/docker.sock:/docker.sock shipyard/deploy restart
  • 升级:docker run -i -t -v /var/run/docker.sock:/docker.sock shipyard/deploy upgrade

9.5.3. 不建议生产使用,可作为学习借鉴

  • 原因1:封装太多,用户无法定制修改(20140903)
  • 原因2:一些细节功能方面,例如不同host的容器打印在一张表里,连排序功能都没有(20140903)

10. 镜像制作

10.1. 远程编译Dockerfile

  • docker -H tcp://xxx:2376 build –force-rm –no-cache -t foo/rhel6.5:1.0 /path/to/ 那么这个/path/to/指的是本地文件,而非远程编译机上的文件。
  • /path/to/Dockerfile文件会在命令执行之初就通过远端2376端口将/path/to/*(Dockerfile所在目录下的所有文件)传送到编译机上
    Sending build context to Docker daemon xx MB过程就是将文件发送过去
    
  • Dockerfile除了支持文件方式外,还支持URL,即/path/to/可以改为http://xxx(尚未测试过)

11. 内置bridge(nat)和自建网络桥接使用区别

  • 内置bridge(nat)
    优点:
    1. 节省IP
    
    缺点:
    1. 需要配套服务注册/发现,否则宿主上端口分配困难,容易冲突。
    2. 由于每个容器暴露的端口都不一致,造成前端路由层nginx配置(proxy_pass)里无法使用dns的方式。
    3. 端口映射要在容器启动时就指定好,后期无法变更。
    4. 测试发现nat不支持websocket。
    
  • 自建桥接网络
    优点:
    1. 每个容器都有独立ip,对外提供服务,如nginx+php,nginx+resin,都可以使用默认的80端口
    2. 由于容器暴露端口都可以使用80端口,因此前端路由层nginx配置(proxy_pass)里可以使用dns的方式。
    3. 无需为了后期端口映射添加而烦恼
    4. 桥接支持websocket
    
    缺点:
    1. 每个容器都需要一个IP(内网ip是不需要钱的)
    

12. Docker Event事件监听

12.1. 方法1:使用remote api

  • 还未找出靠谱的阻塞方式

12.2. 方法2:使用unix socket

  • 测试发现:docker服务关闭时,/var/run/docker.sock文件并不会自动删掉
    使用python连接/var/run/docker.sock文件会一直连着,docker daemon停止后,文件仍在,连接也仍在,因此不适用于事件监听。
    

12.3. 方法3: 使用docker events命令

  • 测试发现:输出内容与api的输出不完全一样,比如docker events输出的时间格式为可读的格式,而api输出的是unix timestamp。
  • 可以通过python的模块os.popen(‘docker events’)来建立监听连接
    # -*- coding: utf-8 -*-
    import os
    handler = os.popen('docker -H 127.0.0.1:2376 events')
    while True:
        res = handler.readline()
        if res:
            print res
        else:
            print "Null"
            exit(1)
    

13. 神器

13.1. nsenter(无需sshd、无需attach也可以登录容器)

  • 原理:进入namespace(通过/proc/xxxx/ns/)
  • 安装:docker run -v /usr/local/bin:/target registry.17173ops.com:5000/jpetazzo/nsenter:latest
    执行成功后会在宿主的/usr/local/bin下生成1个二进制程序nsenter和1个shell脚本docker-enter
    
    docker-enter是对nsenter用法的封装,让使用更加简单
    
  • 使用:docker-enter 容器id(或容器名)
    docker-enter是一个shell脚本,其实是调用nsenter(二进制文件),因此可以直接使用nsenter:
    nsenter --target $PID --mount --uts --ipc --net --pid		# 这个$PID指容器里的任意进程在宿主上的真实PID
    

14. FAQ

14.1. sshd服务起不来

  • docker在源码里就关掉了audit_control(linux Capabilities),而/etc/pam.d/sshd里有一行session required pam_loginuid.so,把这行删掉就可以了

14.2. ulimit无法更改open-file、max processes

  • docker容器默认移除sys_resource(Linux能力),因而ulimit -n设置只能改小无法改大,改大会报错:ulimit: open files: cannot modify limit: Operation not permitted。
  • 红帽7下docker run可以使用–privileged选项来不移除Linux能力,但docker默认移除这个Linux能力肯定是有安全方面的考量,因此尽量别用该选项

红帽6下要使用–privileged,docker版本不能>=1.0.1,否则会报错;stat /dev/.udev/db/cpuid:cpu0: no such file or directory。

  • 解决方法:
    若启动docker使用sysV服务,则在/etc/init.d/functions最开头添加一行:ulimit -u 204800 -HSn 204800
    
    若启动docker使用命令,如docker -d,那么在启动之前先执行ulimit -u 204800 -HSn 204800即可
    

14.3. 改变/var/lib/docker路径

  • 使用–graph参数:docker –graph=/opt/docker -d,会自动生成/opt/docker目录(0700),并在该目录下创建docker相关文件

原来的镜像和容器都找不到了,因为路径改了(原来的镜像是在/var/lib/docker/devicemapper/devicemapper/{data,metadata})

14.4. 将指定镜像标识为latest

  • docker tag 镜像id cyent/rhel6.5:latest

14.5. docker push报错

14.5.1. HTTP code 403 while uploading metadata: invalid character ‘<‘ looking for beginning of value

  • 报错示例:
    [ 14:50:44-root@localhost:vhosts ]#docker push registry.17173ops.com:82/crosbymichael/dockerui
    The push refers to a repository [registry.17173ops.com:82/crosbymichael/dockerui] (len: 1)
    Sending image list
    Pushing repository registry.17173ops.com:82/crosbymichael/dockerui (1 tags)
    511136ea3c5a: Pushing 
    2014/09/01 14:50:46 HTTP code 403 while uploading metadata: invalid character '<' looking for beginning of value
    
  • 解决方法:在nginx配置里注释掉”proxy_set_header Host $host;”

14.5.2. dial tcp 127.0.0.1:5000: connection refused

  • 报错示例:
    [ 18:24:35-root@localhost:~ ]#docker push registry.17173ops.com:81/17173/as6.5-ng1.4:1.4
    The push refers to a repository [registry.17173ops.com:81/17173/as6.5-ng1.4] (len: 1)
    Sending image list
    Pushing repository registry.17173ops.com:81/17173/as6.5-ng1.4 (1 tags)
    511136ea3c5a: Pushing 
    2014/09/01 18:27:27 Failed to upload metadata: Put http://127.0.0.1:5000/v1/images/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json: dial tcp 127.0.0.1:5000: connection refused
    
  • 解决方法:不要这么用,不要套nginx,gunicorn直接对外使用

14.6. CMD 和 ENTRYPOINT的区别

  • CMD是可以在docker run时候被覆盖的,而ENTRYPOINT无法被覆盖
  • CMD通常被用于调试,可以选择不同的CMD,可支持带参数
  • 而ENTRYPOINT通常被用于应用发布,也可支持带参数
  • 举例说明:
    假设CMD和ENTRYPOINT都是/bin/run.sh,run.sh的内容是echo "Hello,$1"
    那么,若为CMD,则docker run -t -i xxx/yyy /bin/ls /root就会列出/root目录下的内容
    若为ENTRYPOINT,则docker run -t -i xxx/yyy /bin/ls /root就会打印出Hello,/bin/ls
    
  • 最重要的区别:ENTRYPOINT是会被继承下去的
    比如做镜像A的时候Dockerfile里写了一行ENTRYPOINT /usr/local/bin/run.sh,那么之后做的镜像(FROM:)的Dockerfile里如果不指定ENTRYPOINT,而是指定CMD,那么这个CMD虽然从inspect里看是存在的,但却是无效的。

【荐】用iopp代替iotop

1、为什么推荐iopp

iotop对内核及python版本都有一定要求,有时候无法用上,这时候就可以使用iopp作为替代方案。在有些情况下可能无法顺利使用iotop,这时候就可以选择iopp了。它的作者是Mark Wong,用C开发,代码仅有532行,非常简洁。

iopp的项目地址:https://github.com/markwkm/iopp

2、安装iopp

下载源码到本地后,执行下面的命令即可完成安装:
cmake CMakeLists.txt
make && make install
 
#或者指定安装的目标路径到 /usr/bin 下
make install DESTDIR=/usr

3、使用iopp

iopp使用起来非常简便,用法:

iopp [-ci] [-k|-m] [delay [count]]

几个常用参数见下,比较简单,就不一一解释了:

-c, --command display full command line
-h, --help display help
-i, --idle hides idle processes
-k, --kilobytes display data in kilobytes
-m, --megabytes display data in megabytes
-u, --human-readable display data in kilo-, mega-, or giga-bytes

iopp输出的结果也比较清晰易懂,简单解释下:

pid 进程ID
rchar 预计发生磁盘读的字节数
wchar 预计发生磁盘写的字节数
syscr I/O读次数
syscw I/O写此书
rbytes 真正发生磁盘读的字节数
wbytes 真正发生磁盘写的字节数
cwbytes 因为清空页面缓存而导致没有发生操作的字节数
command 命令行名称

tpcc-mysql安装、使用、结果解读

TPC-C是专门针对联机交易处理系统(OLTP系统)的规范,一般情况下我们也把这类系统称为业务处理系统。
tpcc-mysql是percona基于TPC-C(下面简写成TPCC)衍生出来的产品,专用于MySQL基准测试。其源码放在launchpad上,用bazaar管理,项目地址:https://code.launchpad.net/~percona-dev/perconatools/tpcc-mysql

一、 下载源码包
安装epel包后以便安装bzr客户端:

rpm -Uvh http://dl.fedoraproject.org/pub/epel/5/i386/epel-release-5-4.noarch.rpm

然后就可以开始安装bzr客户端了:

yum install bzr

之后,就可以开始用bzr客户端下载tpcc-mysql源码了。

cd /tmp
bzr branch lp:~percona-dev/perconatools/tpcc-mysql

MySQL中文网便捷下载地址:

http://imysql.com/wp-content/uploads/2014/09/tpcc-mysql-src.tgz

下载到本地后,先执行 gunzip 解压缩文件,再执行 tar xf 解包,直接 tar zxf 可能会报告异常。

tpcc-mysql的业务逻辑及其相关的几个表作用如下:

New-Order:新订单,主要对应 new_orders 表
Payment:支付,主要对应 orders、history 表
Order-Status:订单状态,主要对应 orders、order_line 表
Delivery:发货,主要对应 order_line 表
Stock-Level:库存,主要对应 stock 表

其他相关表:
客户:主要对应 customer 表
地区:主要对应 district 表
商品:主要对应 item 表
仓库:主要对应 warehouse 表

二、编译安装
编译非常简单,只需要一个 make 即可。

cd /tmp/tpcc-mysql/src
make
如果 make 没有报错,就会在 /tmp/tpcc-mysql 下生成 tpcc 二进制命令行工具 tpcc_load 、 tpcc_start

三、TPCC测试前准备
初始化测试库环境

cd /tmp/tpcc-mysql
mysqladmin create tpcc1000
mysql -f tpcc1000 < create_table.sql

初始化完毕后,就可以开始加载测试数据了

tpcc_load用法如下:
tpcc_load [server] [DB] [user] [pass] [warehouse]
或者
tpcc_load [server] [DB] [user] [pass] [warehouse] [part] [min_wh] [max_wh]

选项 warehouse 意为指定测试库下的仓库数量。

真实测试场景中,仓库数一般不建议少于100个,视服务器硬件配置而定,如果是配备了SSD或者PCIE SSD这种高IOPS设备的话,建议最少不低于1000个

执行下面的命令,开始灌入测试数据:

cd /tmp/tpcc-mysql
./tpcc_load localhost tpcc1000 tpcc_user "tpcc_password" 1000

在这里,需要注意的是 tpcc 默认会读取 /var/lib/mysql/mysql.sock 这个socket 文件。
因此,如果你的 socket 文件不在相应路径的话,可以做个软连接,或者通过TCP/IP的方式连接测试服务器,例如:

cd /tmp/tpcc-mysql
./tpcc_load 1.2.3.4:3306 tpcc1000 tpcc_user "tpcc_password" 1000

加载测试数据时长视仓库数量而定,若过程比较久需要稍加耐心等待。

四、进行TPCC测试
tpcc_start 工具用于tpcc压测,其用法如下:

tpcc_start -h server_host -P port -d database_name -u mysql_user \
 -p mysql_password -w warehouses -c connections -r warmup_time \
 -l running_time -i report_interval -f report_file

几个选项稍微解释下

-w 指定仓库数量
-c 指定并发连接数
-r 指定开始测试前进行warmup的时间,进行预热后,测试效果更好
-l 指定测试持续时间
-i  指定生成报告间隔时长
-f 指定生成的报告文件名

现在我们来开启一个测试案例:

tpcc_start -hlocalhost -d tpcc1000 -u tpcc_user -p "tpcc_password" \
 -w 1000 -c 32 -r 120 -l 3600 \
 -f tpcc_mysql_20140921.log >> tpcc_caseX_20140921.log 2>&1

即:模拟 1000个仓库规模,并发 16个线程进行测试,热身时间为 60秒, 压测时间为 1小时。

真实测试场景中,建议预热时间不小于5分钟,持续压测时长不小于30分钟,否则测试数据可能不具参考意义。

五、TPCC测试结果解读:

发起测试:

./tpcc_start -h 1.2.3.4 -P 3306 -d tpcc10 -u tpcc -p tpcc \
 -w 10 -c 64 -r 30 -l 120 \
 -f tpcclog_201409211538_64_THREADS.log >> tpcc_noaid_2_20140921_64.log 2>&1

测试结果输出如下:

-- 本轮tpcc压测的一些基本信息
***************************************
*** ###easy### TPC-C Load Generator ***
***************************************
option h with value '1.2.3.4'   -- 主机
option P with value '3306'             -- 端口
option d with value 'tpcc10'         -- 数据库
option u with value 'tpcc'             -- 账号
option p with value 'tpcc'             -- 密码
option w with value '10'                 -- 仓库数
option c with value '64'                 -- 并发线程数
option r with value '30'                 -- 数据预热时长
option l with value '120'               -- 压测时长
option f with value 'tpcclog_20140921_64_THREADS.res'  -- 输出报告日志文件

     [server]: 1.2.3.4
     [port]: 3306
     [DBname]: tpcc10
       [user]: tpcc
       [pass]: tpcc
  [warehouse]: 10
 [connection]: 64
     [rampup]: 30 (sec.)
    [measure]: 120 (sec.)

RAMP-UP TIME.(30 sec.)

-- 预热结束,开始进行压测
MEASURING START.

-- 每10秒钟输出一次压测数据
  10, 8376(0):2.744|3.211, 8374(0):0.523|1.626, 838(0):0.250|0.305, 837(0):3.241|3.518, 839(0):9.086|10.676
  20, 8294(0):2.175|2.327, 8292(0):0.420|0.495, 829(0):0.206|0.243, 827(0):2.489|2.593, 827(0):7.214|7.646
…
 110, 8800(0):2.149|2.458, 8792(0):0.424|0.710, 879(0):0.207|0.244, 878(0):2.461|2.556, 878(0):7.042|7.341
 120, 8819(0):2.147|2.327, 8820(0):0.424|0.568, 882(0):0.208|0.237, 881(0):2.483|2.561, 883(0):7.025|7.405
-- 以逗号分隔,共6列
-- 第一列,第N次10秒
-- 第二列,总成功执行压测的次数(总推迟执行压测的次数):90%事务的响应时间|本轮测试最大响应时间
-- 第三列,新订单业务成功执行次数(推迟执行次数):90%事务的响应时间|本轮测试最大响应时间
-- 第四列,支付业务的结果,后面几个的意义同上
-- 第五列,发货业务的结果,后面几个的意义同上
-- 第六列,库存业务的结果,后面几个的意义同上

-- 压测结束
STOPPING THREADS................................................................

   -- 第一次粗略结果统计
  [0] sc:100589  lt:0  rt:0  fl:0    -- New-Order,新订单业务成功(success,简写sc)次数,延迟(late,简写lt)次数,重试(retry,简写rt)次数,失败(failure,简写fl)次数
  [1] sc:100552  lt:0  rt:0  fl:0    -- Payment,支付业务统计,其他同上
  [2] sc:10059  lt:0  rt:0  fl:0    -- Order-Status,订单状态业务统计,其他同上
  [3] sc:10057  lt:0  rt:0  fl:0    -- Delivery,发货业务统计,其他同上
  [4] sc:10058  lt:0  rt:0  fl:0    -- Stock-Level,库存业务统计,其他同上
 in 120 sec.

    -- 第二次粗略统计结果,其他同上
  [0] sc:100590  lt:0  rt:0  fl:0 
  [1] sc:100582  lt:0  rt:0  fl:0 
  [2] sc:10059  lt:0  rt:0  fl:0 
  [3] sc:10057  lt:0  rt:0  fl:0 
  [4] sc:10059  lt:0  rt:0  fl:0 

 (all must be [OK])       -- 下面所有业务逻辑结果都必须为 OK 才行
 [transaction percentage]
        Payment: 43.47% (>=43.0%) [OK]      -- 支付成功次数(上述统计结果中 sc + lt)必须大于43.0%,否则结果为NG,而不是OK
   Order-Status: 4.35% (>= 4.0%) [OK]       -- 订单状态,其他同上
       Delivery: 4.35% (>= 4.0%) [OK]       -- 发货,其他同上
    Stock-Level: 4.35% (>= 4.0%) [OK]       -- 库存,其他同上
 [response time (at least 90% passed)]      -- 响应耗时指标必须超过90%通过才行
      New-Order: 100.00%  [OK]              -- 下面几个响应耗时指标全部 100% 通过
        Payment: 100.00%  [OK]
   Order-Status: 100.00%  [OK]
       Delivery: 100.00%  [OK]
    Stock-Level: 100.00%  [OK]


                 50294.500 TpmC                      -- TpmC结果值

script目录下的一些脚本主要是一些性能数据采集以及分析的,可以自行摸索下怎么用。

其他推荐:
TPCC-MySQL使用手册

搜狐视频:MySQL DBA成长之路 – tpcc-mysql安装、使用、结果解读

redis主从实例间快速迁移案例

操作步骤
1. 在slave server新建一个redis实例,建议认证密码(“requirepass”)与master server一致,但是不要在redis.conf里面设置同步配置(“slaveof”“masterauth”)
2. 启动新的slave server;
3. 在线配置同步:
config set masterauth xxxxxxxx (config set masterauth “master server的认证密码”)
slaveof 1.2.3.4 6379 (slaveof “master server IP” “master server port” )
4. 使用info指令确认同步完成(这个步骤所需时间与数据量成正比);
5. 设置slave server可写:
config set slave-read-only no
6. 至此迁移完成,随后就可以进行各种切换(或者IP,或者DNS,或者直接修改代码中的数据库连接串);
7. 确认原先的master server无新数据写入之后,将slave server提升成为新的主节点,并使用info观察:
slaveof no one
8. 修改原slave server的数据指向(如果第6步是进行IP切换,那这步就可以省略,旧的slave server会自动切换master)。

注意事项
1. 在第6步切换的时候需要注意配置更新延时或者更新不一致,导致存在redis双写导致数据不一致或者逻辑功能异常等现象;
2. 迁移切换对于redis来说依然是bgsave->sync->load的过程,尽量选在闲时进行处理。

总结
比起传统的:环境部署–>bgsave–>rsync–>启动服务这样的迁移方式,上文提到的不停服务在线切换其实原理是一样,只是操作相对复杂那么一些些,但是使用上述方法可以有效缩短服务不可用时间。

一个php进程cpu %nice很高的原因详解

一、 现象描述:

1、 CPU的%user、%sys占用的CPU不高,但%nice占用了大量的CPU资源,最高占用CPU的60%以上;
2、 ps -elf中PRI为90,NI为10,top看到PR值为30,NI 10;

问题:为什么从ps中看到的priority值和top中的不同?
top中的PR是:The priority of the task(取值范围:-20最高,19最低)
ps中的PRI是:realtime priority(根据 nice( ) 和setpriority( ) 计算,Static priority 取值1-99,Dynamic priority还没有看懂)

3、sar信息收集

CPU:
Linux 2.6.32-279.el6.x86_64 (web2.17173ops.com) 05/09/2014 _x86_64_ (8 CPU)

12:00:04 AM CPU %user %nice %system %iowait %steal %idle
12:01:01 AM all 0.39 26.67 1.84 30.15 0.00 40.95
12:02:03 AM all 0.30 24.07 2.74 30.10 0.00 42.80
12:03:01 AM all 0.27 23.61 1.65 34.48 0.00 40.00
12:04:02 AM all 0.26 23.37 1.96 34.87 0.00 39.54
12:05:04 AM all 0.31 24.08 3.74 36.66 0.00 35.21
12:06:02 AM all 0.43 21.96 2.09 34.93 0.00 40.59
12:07:16 AM all 0.25 23.66 2.07 59.63 0.00 14.38
12:08:02 AM all 0.33 30.21 2.01 36.88 0.00 30.57

二、服务器硬/软件:

Product Name: PowerEdge R410
CPU:Intel(R) Xeon(R) CPU E5606 @ 2.13GHz
Memory:24GB 4*6
OS:Red Hat Enterprise Linux Server release 6.3 (Santiago)
Web server:nginx version: nginx/1.4.4
CGI server:php-5.3.21

三、 判断问题:
1、 NICE资源一般是用户端控制的行为产生;
2、 除非程序中有大量的使用sleep,或者是调用了nice等函数,对自定义了优先级别,但一般程序不会这么变态;

四、 查找问题过程:
1、习惯性认为是程序开发的问题,在代码中不断查找类似sleep、nice的字眼,浪费了一些时间;
2、ps -elf | grep master,发现php-fpm启动时间不一致,有的是03:57,有的是4:03,如果是crontab定义的,时间肯定不会这么不一致;
3、查找/etc/crontab,文件里面没有定义任何定时任务;
4、RHEL6的定时任务被anacrontab接管,anacrontab是crontab的补充,内容如下:(问题也就出在下面红色+备注的那行)

# cat /etc/anacrontab 
# /etc/anacrontab: configuration file for anacron

# See anacron(8) and anacrontab(5) for details.

SHELL=/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
# the maximal random delay added to the base delay of the jobs
RANDOM_DELAY=45
# the jobs will be started during the following hours only
START_HOURS_RANGE=3-22

#period in days delay in minutes job-identifier command
1 5 cron.daily nice run-parts /etc/cron.daily ## 日志切割定时任务在此
7 25 cron.weekly nice run-parts /etc/cron.weekly
@monthly 45 cron.monthly nice run-parts /etc/cron.monthly

五、 思考、优化:
1、不建议在服务器上使用anacrontab,继续使用crontab;(RHEL5不存在此问题);
2、其实anacrontab使用nice指令也不会出现问题,主要是和php-fpm启动脚本中的restart配合导致这种情况的发生;
3、php-fpm使用reload或者是kill -USR2,nice都不会生效,会按原PID的优先等级。因为手工启动php-fpm时我们肯定不会带上nice。