Docker 基础知识之 Namespace, Cgroup

最近工作上需要使用 Docker,在阅读「第一本 Docker 书」后了解了如何成为 Docker 的用户,但对 Docker 中用到技术却不甚了解。都说 Docker 是「新瓶装旧球」,文中笔者将学习到的 Docker 基础技术中的 Namespace,Cgroup 与 AUFS 记录如下。

Namespace

Linux Namespace 是 Linux 内核提供的一个功能,可以实现系统资源的隔离,如:PID、User ID、Network 等。Linux 中的 chroot 命令可以将当前目录设置为根目录,使得用户的操作被限制在当前目录之下而不影响其他目录。

假设我们成立了一家向外售卖计算资源的公司,用户购买了一个实例在运行自己的应用。如果某些用户能够进入到其他人的实例中,修改或关闭其他实例中应用的状态,那么就会导致不同用户之间相互影响;用户的某些操作可能需要 root 权限,假如我们给每个用户都赋予了 root 权限,那么我们的机器也就没有任何安全性可言了。使用 Namespace,Linux 可以做到 UID 级别的隔离,也就是说,UID 为 n 的用户在自己的 Namespace 中是有 root 权限的,但是在真实的物理机上,他仍然是 UID 为 n 的用户。

目前 Linux 共实现了 6 种不同的 Namespace。

未分类

UTS Namespace

UTS namespaces allow a single system to appear to have different host and domain names to different processes.

UTS(UNIX Timesharing System) Namespace 可以用来隔离 nodename 和 domainname 两个系统标识。在 UTS Namespace 中,每个 Namespace 可以有自己的 hostname。

我们运行下面程序:

func main() {
    cmd := exec.Command("zsh")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS,
    }
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    if err := cmd.Run(); err != nil {
        log.Fatal(err)
    }
}

这段代码主要是通过系统调用 clone,并传入 CLONE_NEWUTS 作为参数创建一个新进程,并在新进程内运行 zsh 命令。在 Ubuntu 14.04 上运行这段代码,就可以进入一个交互环境,在环境中运行 ps -af –forest 就可以看到如下的进程树:

未分类

验证下父进程和子进程是否在同一个 UTS Namespace 中:

未分类

可以看到他们的 UTS Namespace 的编号不同。因为 UTS Namespace 对 hostname 做了隔离,所以在这个环境内修改 hostname 不会影响外部主机。

在目前的 zsh 环境中我们修改 hostname 并打印:

未分类

在宿主机上打印 hostname:

未分类

可以看到,外部的 hostname 没有被内部的修改所影响。

IPC Namespace

IPC namespaces isolate processes from SysV style inter-process communication.

IPC(Interprocess Communication) Namespace 用来隔离 System V IPC 和 POSIX message queues。每一个 IPC Namespace 都有自己的 System V IPC 和 POSIX message queue。

我们在上一段代码的基础上增加 CLONE_NEWIPC 标识,表示我们要创建 IPC Namespace。

func main() {
    cmd := exec.Command("zsh")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC,
    }
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    if err := cmd.Run(); err != nil {
        log.Fatal(err)
    }
}

在宿主器机查看并创建一个 message queue:

未分类

运行代码并查看 message queue:

未分类

PID Namespace

The PID namespace provides processes with an independent set of process IDs (PIDs) from other namespaces.

PID(Process ID) Namespace 可以用来隔离进程 ID。同一个进程在不同的 PID Namespace 中可以拥有不同的 PID。在 Docker Container 中,使用 ps -ef 可以看到启动容器的进程 PID 为 1,但是在宿主机上,该进程却又有不同的 PID。

继续在代码上添加 CLONE_NEWPID 为子进程创建 PID Namespace。

func main() {
    cmd := exec.Command("zsh")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID,
    }
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    if err := cmd.Run(); err != nil {
        log.Fatal(err)
    }
}

运行代码,首先在宿主机上查看进程树:

未分类

可以看到 zsh 的 PID 为 11321。在 Namespace 中打印进程 PID:

未分类

可以看到,打印出的当前 Namespace 的 PID 为 1,也就是说 11321 的进程被映射到 Namespace 中后 PID 为 1。

Mount Namespace

Mount namespaces control mount points.

Mount Namespace 用来隔离各个进程看到的挂载点视图。在不同的 Namespace 中,看到的挂载点文件系统层次是不一样的。在 Mount Namespace 中调用 mount 和 unmount 仅仅会影响当前 Namespace 内的文件系统,而对全局文件系统是没有影响的。

在代码中,我们继续加入 CLONE_NEWNS 标识。

func main() {
    cmd := exec.Command("zsh")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS,
    }
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    if err := cmd.Run(); err != nil {
        log.Fatal(err)
    }
}

首先运行代码,然后查看 /proc 的文件内容:

未分类

可以看到宿主机的 /proc 中文件较多,其中的数字是对应进程的相关信息。下面,将 /proc mount 到 Namespace 中。

未分类

可以看到现在以 PID 命名的文件夹明显减少。下面使用 ps -ef 查看系统进程:

未分类

可以看到,在当前的 Namespace 中,zsh 是 PID 为 1 的进程。这就说明当前 Namespace 中的 mount 和外部是隔离的,mount 操作没有影响到外部。Docker 的 volumn 正是利用了这个特性。

User Namespace

User namespaces are a feature to provide both privilege isolation and user identification segregation across multiple sets of processes.

User Namespace 主要是隔离用户的用户组 ID。也就是说,一个进程的 User ID 和 Group ID 在 User Namespace 内外可以是不同的。比较常用的是,在宿主机上以一个非 root 用户运行创建一个 User Namespace,然后在 User Namespace 中被映射为了 root 用户。这意味着这个进程在 User Namespace 中有 root 权限,但是在宿主机上却没有 root 权限。

继续修改代码,添加 CLONE_NEWUSER 标识。

func main() {
    cmd := exec.Command("zsh")
    cmd.SysProcAttr = &syscall.SysProcAttr{
    Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID |
        syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER,
    }
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    if err := cmd.Run(); err != nil {
        log.Fatal(err)
    }
    os.Exit(1)
}

首先在宿主机上查看当前用户和用户组:

未分类

接下来运行程序,并查看用户组:

未分类

可以看到,UID 是不同的,说明 User Namespace 生效了。

Network Namespace

Network namespaces virtualize the network stack. On creation a network namespace contains only a loopback interface.

Network Namespace 用来隔离网络设置、IP 地址和端口号等网络栈的 Namespace。Network Namespace 可以让每个容器拥有自己独立的网络设备,而且容器内的应用可以绑定到自己的端口,每个 Namespace 的端口都不会有冲突。在宿主机搭建网桥后,就能很方便地实现容器之间的通信。

我们继续在代码基础上添加 CLONE_NEWNET 标识。

func main() {
    cmd := exec.Command("sh")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID |
        syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER | syscall.CLONE_NEWNET,
    }
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    if err := cmd.Run(); err != nil {
        log.Fatal(err)
    }
    os.Exit(1)
}

首先,在宿主机上查看自己的网络设备:

未分类

可以看到在宿主机上有 eth0 和 lo 等网络设备。下面,运行程序,并运行 ifconfig:

未分类

我们发现,在 Namespace 中什么网络设备都没有。这可以断定 Namespace 与宿主机之间的网络是处于隔离状态的。

## Cgroups

Linux Namespace 帮助进程隔离出自己的单独空间,而 Cgroups 则可以限制每个空间的大小。Cgroups 提供了对一组进程及将来子进程的资源限制、控制和统计的能力。

Cgroups 有三个组件:

  • cgroup 负责对进程分组管理,一个 cgroup 包含一组进程并可以设置进程参数
  • subsystem 是一组资源控制模块,可以关联到 cgroup 上,并对 cgroup 中的进程做出相应限制。
  • hierarchy 可以把一组 cgroup 串成一个树状结构,这样 cgroup 可以做到继承。

Cgroups 中的 hierarchy 是一种树状结构,Kernel 为了使得对 Cgroups 的配置更加直观,通过一个虚拟的树状文件系统配置 Cgroups 的,通过层级的目录虚拟出 cgroup 树。我们可以在系统上做实验:

1.首先,创建并挂载一个 hierarchy

未分类

  • cgroup.clone_children,cpuset 的 subsystem 会读取这个配置文件,如果这个值是 1,子 cgroup 才会继承父 cgroup 的 cputset 的配置
  • cgroup.procs 是树中当前节点 cgroup 中的进程组 ID
  • notify_on_release 和 release_agent 会一起使用。notify_on_release 标识当这个 cgroup 最后一个进程退出的时候是否执行了 release_agent;release_agent 使进程退出后自动清理掉不再使用的 cgroup
  • tasks 标识该 cgroup 下的进程 ID,将进程 ID 写入 tasks 文件中,便会将相应进程加入到这个 cgroup 中

2.在刚创建好的 hierarchy 上 cgroup 的根节点中拓展出两个子 cgroup

未分类

可以看到在 cgroup 目录下创建文件夹的时候,Kernel 会把文件夹标记为子 cgroup,她们继承父 cgroup 的属性。

3.在 cgroup 中添加和移动进程只需要将进程 ID 写到或移动到 cgroup 节点的 tasks 文件中即可

未分类

这样,我们就把当前的 3217 进程加入到 cgroup-test:/cgroup-1 中了

4.通过 subsystem 限制 cgroup 中的进程的资源。我们使用系统为每个 subsystem 默认创建的 hierarchy,如 memory 的 hierarchy 来完成实验。

未分类

未分类

可以看到系统总的内存为 2GB,其中 stess 只能占用到 5% 左右,也就是 100MB。

基于Docker搭建Percona XtraDB Cluster数据库集群

本文实验的环境参数

  • 阿里云ECS Centos7.5
  • Docker version 18.06.0-ce
  • percona/percona-xtradb-cluster:5.7

Percona XtraDB Cluster的镜像下载地址:https://hub.docker.com/r/percona/percona-xtradb-cluster/

怎么使用Docke和下载镜像,请查看Docker的官方文档https://docs.docker.com/

接下来搭建三个容器节点

1、创建外部不可访问的Docker内部网络,使用端口映射开放外部访问

docker network create pxc-network

2、创建容器

docker volume create v1
docker volume create v2
docker volume create v3

3、创建第一个节点(因为初始化集群,所以需要等待一会,在创建第二个节点)

docker run -d 
-p 3306:3306 
-e MYSQL_ROOT_PASSWORD=abc123456 
-e CLUSTER_NAME=PXC 
-e XTRABACKUP_PASSWORD=abc123456 
-v v1:/var/lib/mysql 
--privileged 
--name=node1 
--net=pxc-network 
percona/percona-xtradb-cluster:5.7

4、创建第二个节点并加入集群

docker run -d 
-p 3307:3306 
-e MYSQL_ROOT_PASSWORD=abc123456 
-e CLUSTER_NAME=PXC 
-e XTRABACKUP_PASSWORD=abc123456 
-e CLUSTER_JOIN=node1 
-v v2:/var/lib/mysql 
--privileged 
--name=node2 
--net=pxc-network 
percona/percona-xtradb-cluster:5.7

5、创建第三个节点并加入集群

docker run -d 
-p 3308:3306 
-e MYSQL_ROOT_PASSWORD=abc123456 
-e CLUSTER_NAME=PXC 
-e XTRABACKUP_PASSWORD=abc123456 
-e CLUSTER_JOIN=node1 
-v v3:/var/lib/mysql 
--privileged 
--name=node3 
--net=pxc-network 
percona/percona-xtradb-cluster:5.7

6、使用Navicat等客户端工具访问上面上个节点的数据库,地址是宿主机的地址,端口是每个节点映射的端口,然后创建数据库测试PXC运行情况。

在docker中使用ansible来源码编译nginx服务

说明:
1)在VM上装了一个4核8G的centos7.5系统
2)docker版本为 18.06.0-ce

docker的安装不再讲述

1、创建一个带有含有ssh的镜像,通过编写Dockerfile

# Set the base image to centos
FROM centos:latest
MAINTAINER fei
#mount volume
VOLUME ["/root/docker/ansible-demo/volume2"]
################## BEGIN INSTALLATION ######################
#install EPEL
RUN rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm 
&& rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7 
&& yum install -y yum-priorities
RUN yum install -y sudo
RUN yum install -y 
net-tools 
openssh-clients 
openssh-server 
ansible 
vim
################## END INSTALLATION ######################
# 将sshd的UsePAM参数设置成no,优化ssh连接
RUN sed -i 's/UsePAM yes/UsePAM no/g' /etc/ssh/sshd_config
# 修改root用户密码,这里密码为:devilf
RUN echo "root:devilf"|chpasswd
RUN ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key
RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
# 启动sshd服务并且暴露22端口
RUN mkdir /var/run/sshd
EXPOSE 22
ENTRYPOINT ["/usr/sbin/sshd","-D"]

2、开始构建镜像

docker build --no-cache -t fei/centos:ssh_ansible .

3、启动容器(需要开启特权模式,否则会报错:Failed to get D-Bus connection: Operation not permitted)

docker run -itd -p 20021:22 --privileged=true --name node1 fei/centos:ssh_ansible
docker run -itd -p 20022:22 --privileged=true --name node2 fei/centos:ssh_ansible
...
...
docker run -itd -p 20020:22 --privileged=true --name ansible_server fei/centos:ssh_ansible

4、配置ansible主机清单,并建立互信关系

修改ansible.cfg文件,将默认的hosts文件改为一个目录,修改为:
inventory     = /etc/ansible/conf.d

设置清单

# cat conf.d/docker
[nodes]
172.17.0.2
172.17.0.3
172.17.0.4
172.17.0.5

生成密钥

ssh-keygen

下发密钥

ssh-copy-id [email protected]

5、测试

ansible nodes -m ping

注意:
查看容器IP的方法:

docker inspect --format '{{ .NetworkSettings.IPAddress }}' container_id

停止容器:

docker stop container_id

删除容器:

docker container rm container_id

下面就要开始通过playbook来源码编译安装nginx

可以针对所有的服务安装创建一个专门的目录,例如这里安装nginx,可以创建一个目录,目录结构为:

tree roles/
roles/
├── conf
│   ├── default
│   ├── files
│   ├── handlers
│   │   └── main.yml
│   ├── meta
│   ├── tasks
│   │   └── main.yml
│   ├── templates
│   │   └── temp_server.conf
│   └── vars
│       └── main.yml
├── install
│   ├── default
│   ├── files
│   │   └── nginx-1.12.0.tar.gz
│   ├── handlers
│   │   └── main.yml
│   ├── meta
│   ├── tasks
│   │   └── main.yml
│   ├── templates
│   │   ├── nginx.conf
│   │   ├── web1.conf
│   │   └── web2.conf
│   └── vars
│       └── main.yml
├── nginx.retry
├── nginx.yaml
└── site.yml

分为两部分,conf目录主要是方便增加站点,存放配置文件;install目录主要是为了安装nginx,该目录下会存放安装所用的源码包,配置文件等
install目录下定义一个任务:

# cat tasks/main.yml
- name: cp nginx package to remote host
  copy: src=nginx-1.12.0.tar.gz dest=/tmp/nginx-1.12.0.tar.gz   #去files目录中拉取源码包
  tags: cp-nginx-pkg
- name: tar nginx package
  shell: cd /tmp; tar zxf nginx-1.12.0.tar.gz
- name: install nginx depend pkg
  yum: name={{ item }} state=latest       #item是一个变量,用来指定下面的一些依赖包名
  with_items:
    - openssl-devel
    - pcre-devel
    - gcc
    - gcc-c++
    - autoconf
    - automake
    - libtool
    - make
    - cmake
    - zlib
    - zlib-devel
    - openssl
    - pcre-devel
    - libxslt-devel
- name: install nginx
  shell: cd /tmp/nginx-1.12.0; ./configure --user=www --group=www --prefix=/usr/local/nginx 
         --with-http_stub_status_module 
         --with-http_ssl_module 
         --with-pcre && make && make install
- name: cp conf
  template: src=nginx.conf dest=/usr/local/nginx/conf/nginx.conf     #这个是去templates目录中拉取配置文件
  tags: nginx-conf
- name: cp shell
  copy: src=/ansible/script/create_users.sh dest=/tmp/create_users.sh    #这个脚本的目的是检测目标机器是否已经存在所建的用户,如果存在机会创建用户会报错
- name: create nginx user
  shell: /bin/bash /tmp/create_users.sh
  tags: add-nginx
  notify: start nginx service

上面脚本内容:

# cat /ansible/script/create_users.sh
#!/bin/bash
name="www"
num=$(grep -c $name /etc/passwd)
if [ $num -eq 0 ];then
        groupadd $name
        useradd -g $name $name -s /sbin/nologin
fi

给nginx的主配置文件指定一个端口,通过设置一个变量,后面主配置里面会去引用

# cat vars/main.yml
ngxport: "8080"

主配置文件

# cat templates/nginx.conf
user  www;
worker_processes  {{ ansible_processor_vcpus }};

events {
    worker_connections  65535;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format main  '$remote_addr - $remote_user[$time_local] "$request" '
                      '$status $body_bytes_sent"$http_referer" '
                      '"$http_user_agent""$http_x_forwarded_for"';
    access_log logs/access.log  main;

    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       {{ ngxport }};  
        server_name  www.a.com;
        access_log logs/a.com;

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
   include conf.d/*.conf;
}

定义触发器

# cat handlers/main.yml
- name: start nginx service
  shell: /usr/local/nginx/sbin/nginx

我们这里要新增一个站点做测试,需要修改的目录(需要切换到conf目录中)有:
定义变量,用于配置文件的引用:

# cat vars/main.yml
server_name: "www.a.com"    #每次新增站点时,可以修改此域名
root_dir: "/data/web"

因为新增站点时,是基于域名的虚拟主机,所以端口均为默认的80端口
编写新增站的配置文件:

# cat templates/temp_server.conf
server
{
listen 80;
server_name {{server_name}};
index index.php index.html;
root {{root_dir}};
}

在var目录中定义变量:

cat main.yml
server_name: "www.a.com"
root_dir: "/data/web"

编写配置nginx的tasks步骤哦:

cd tasks
cat main.yml
- name: create vhosts
  shell: mkdir -p /usr/local/nginx/conf/conf.d/
  tags: create_dir
- name: cp file nginx.conf
  template: src=temp_server.conf dest=/usr/local/nginx/conf/conf.d/{{server_name}}.conf
  tags: ngxconf
  notify: reload nginx service

定义角色路径

#回到roles的上级目录下
cat nginx.yaml
- hosts: web1
  remote_user: root
  roles:
    - install
    - conf

测试:

ansible-playbook -C nginx.yaml

测试通过后可以真正去执行

ansible-playbook nginx.yaml

为所有PHP-FPM容器构建单独的Nginx Docker镜像

最近,原文作者一直在使用Docker容器来开发PHP微服务套件。一个问题是PHP应用已经搭建,可以和PHP-FPM和Nginx(取代了简单的Apche/PHP环境)一起工作,因此每个PHP微服务需要两个容器(以及两个Docker镜像):一个PHP-FPM容器和一个NGinx容器。
这个应用运行了6个以上的服务,如果做个乘法,在开发和生产之间会有约30个容器。作者决定构建一个单独的NGinx Docker镜像,它可以使用PHP-FPM的主机名作为环境变量并运行单独的配置文件,而没有为每个容器构建单独的NGinx镜像。

未分类

在本文中,原文作者简要说明从上图中的方法1到方法2的转换,最后采用的方案中采用了一种新的定制Docker镜像。该镜像的代码是开源的,如果读者碰到类似问题,可以随时签出该部分代码。

为什么用 NGinx?

NGinx和PHP-FPM配合使用能使PHP应用的性能更好,但不好的是和PHP Apache镜像不同,PHP-FPM Docker镜像缺省并没有和NGinx进行绑定。如果需要通过NGinx容器和PHP-FPM连接,需要在NGind配置里为该后端增加DNS记录。比如,如果名为php-fpm-api的PHP-FPM容器正在运行,NGinx配置文件应该包含下面部分:

    location ~ .php$ {
        fastcgi_split_path_info ^(.+.php)(/.+)$;
        # This line passes requests through to the PHP-FPM container
        fastcgi_pass php-fpm-api:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

如果只服务于单独的NGinx容器,NGinx配置中容器名字写死还可以接受,但如上所述,需要允许多个NGinx容器,每个对应于一个PHP服务。创建一个新的NGinx镜像(以后需要进行维护和升级)会有些痛苦,即使管理一批不同的数据卷,仅仅改变变量名看起来也有很多工作。

第一种方案: 使用Docker文档中的方法

最初,作者认为这会很简单。Docker文档中有少许的几个章节讨论如何使用envsubst来完成该工作,但不幸的是,在其NGinx配置文件中,这种方法不奏效。

vhosts.conf

server {
    listen 80;
    index index.php index.html;
    root /var/www/public;
    client_max_body_size 32M;
    location / {
        try_files $uri /index.php?$args;
    }
    location ~ .php$ {
        fastcgi_split_path_info ^(.+.php)(/.+)$;
        fastcgi_pass ${NGINX_HOST}:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

该vhosts.conf文件使用了NGinx内置变量,因此当依照文档运行Docker命令(/bin/bash -c “envsubst < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g ‘daemon off;’”)时,得到错误提示$uri和$fastcgi_script_name没有定义。这些变量通常通过NGinx传入,因此不能简单的识别出他们是什么并传给自身,而且这使容器的动态性变差。

用另一个Docker镜像来救急,差点成功

接下来,作者开始研究不同的NGinx镜像。找到的两个,但它们都在随后的几年中都没有任何更新。作者开始使用martin/nginx,试图找到可以工作的原型。
Martin镜像和其它镜像有点不一样,因为它要求特定的文件夹结构。在root下增加Dockerfile:

FROM martin/nginx

接下来,我添加了一个app/空目录和conf/目录,conf/目录下只有一个文件vhosts.conf:

server {
    listen 80;
    index index.php index.html;
    root /var/www/public;
    client_max_body_size 32M;
    location / {
        try_files $uri /index.php?$args;
    }
    location ~ .php$ {
        fastcgi_split_path_info ^(.+.php)(/.+)$;
        fastcgi_pass $ENV{"NGINX_HOST"}:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

这个文件和之前的配置文件几乎一样,除了有一行的改动:

fastcgi_pass $ENV{“NGINX_HOST”}:9000;。现在想要启动带命名为php-fpm-api的PHP容器的NGinx容器,就可以构建一个新的镜像,让它在以下环境变量下运行:

docker build -t shiphp/nginx-env:test .
docker run -it --rm -e NGINX_HOST=php-fpm-api shiphp/nginx-env:test

它可以正常工作了。但是,这种方法有两个困扰的地方:
1. 正在使用的基础镜像已经有两年了。这会引入安全和性能风险。
2. 有个空的/app目录看起来并不必需,因为文件会被存储在一个不同的目录中。

最终解决方案

作者认为作为定制解决方案,从Martin镜像开始比较好,因此给项目建了分叉,创建了新的NGinx基础镜像并修复了上述两个问题。现在,如果要在NGinx容器中允许动态命名的后端,可以参照:

# 从Docker Hub得到最新版本
docker pull shiphp/nginx-env:latest
# 运行名为"php-fpm-api"的PHP容器 
docker run --name php-fpm-api -v $(pwd):/var/www php:fpm
# 允许链接到PHP-FPM容器的NGinx容器
docker run --link php-fpm-api -e NGINX_HOST=php-fpm-api shiphp/nginx-env

如果想增加自己的文件或NGinx配置文件,来定制镜像,用Dockerfile来扩展它就可以:

FROM shiphp/nginx-env

ONBUILD ADD <PATH_TO_YOUR_CONFIGS> /etc/nginx/conf.d/

...

现在所有的PHP-FPM容器都使用了它们自己的Docker镜像实例,这样在升级NGinx,改变权限或做某些调整时,就变得非常轻松了。 所有的代码都在Github上,如果读者看到任何问题或有改进建议,可以直接创建一个问题单。如果有疑问或任何Docker相关的,可以在Twitter上找到我继续探讨。

查看英文原文:https://www.shiphp.com/blog/2018/nginx-php-fpm-with-env

自建 Laravel 的 Docker 开发环境

好久没写东西,今天说一说怎么自建一个 Laravel 运行的 Docker 环境。
市面上最出名的莫过于「laradock」https://github.com/laradock/laradock

未分类

Docker PHP development environment.
使用参考:http://laradock.io/

既然是「自建」,那我们可以参考这个,最小化满足 Laravel 运行的需要。

下面是我罗列出的基本条件:

  1. 软件:PHP 7.2、Nginx、MySQL、Composer、NPM or Yarn 等等;
  2. 使用国内镜像;使用国内镜像;使用国内镜像;
  3. 易于扩展使用,如随时可以切换 PHP 版本,或者 Apache 和 Nginx 切换使用。

Docker-Compose

要达到可扩展行,和「laradock」一样,使用 Docker-Compose 编排的方式,将核心的几个 image 组装在一起。

php-fpm

这里我们使用的是「DaoCloud」加速镜像 —— 7.2-fpm-alpine。

该版本既用 PHP 7.2 版本,而且 alpine 最小化系统,可以基于此,安装环境需要的额外工具:如,composer、nodejs、python、yarn 等。
FROM daocloud.io/php:7.2-fpm-alpine

MAINTAINER coding01 <[email protected]>

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

RUN apk add --no-cache --virtual .build-deps 
        $PHPIZE_DEPS 
        curl-dev 
        imagemagick-dev 
        libtool 
        libxml2-dev 
        postgresql-dev 
        sqlite-dev 
    && apk add --no-cache 
        curl 
        git 
        imagemagick 
        mysql-client 
        postgresql-libs 
    && pecl install imagick 
    && docker-php-ext-enable imagick 
    && docker-php-ext-install 
        curl 
        iconv 
        mbstring 
        pdo 
        pdo_mysql 
        pdo_pgsql 
        pdo_sqlite 
        pcntl 
        tokenizer 
        xml 
        zip 
    && curl -s https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin/ --filename=composer 
    && apk del -f .build-deps

# 修改 composer 为国内镜像
RUN composer config -g repo.packagist composer https://packagist.laravel-china.org

# install prestissimo
RUN composer global require "hirak/prestissimo"

# install laravel envoy
RUN composer global require "laravel/envoy"

#install laravel installer
RUN composer global require "laravel/installer"

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

RUN apk update && apk add -u nodejs libpng-dev python

ENV PATH /root/.yarn/bin:$PATH

RUN apk update 
  && apk add curl bash binutils tar 
  && rm -rf /var/cache/apk/* 
  && /bin/bash 
  && touch ~/.bashrc 
  && curl -o- -L https://yarnpkg.com/install.sh | bash 
  && yarn config set registry 'https://registry.npm.taobao.org' 
  && npm install -g cnpm --registry=https://registry.npm.taobao.org

WORKDIR /var/www

其中安装 alpine 系统插件,我们使用 mirrors.aliyun.com 阿里云镜像。

php:7.2-fpm-alpine 具体使用,可以参考:dashboard.daocloud.io/packages/01…

nginx

我们使用 nginx,主要是将网站的配置文件载入 nginx 中。

FROM daocloud.io/nginx:1.13-alpine

MAINTAINER coding01 <[email protected]>

ADD vhost.conf /etc/nginx/conf.d/default.conf

WORKDIR /var/www

剩下就是连接这些 images。最后看看 docker-compose.yml 文件内容:

version: '2'
services:

  # The Application
  app:
    build:
      context: ./
      dockerfile: app.dockerfile
    working_dir: /var/www
    volumes:
      - ../:/var/www
    environment:
      - "DB_PORT=3306"
      - "DB_HOST=database"
      - "REDIS_HOST=redis"
      - "REDIS_PORT=6379"

  # The Web Server
  web:
    build:
      context: ./
      dockerfile: web.dockerfile
    working_dir: /var/www
    volumes_from:
      - app
    ports:
      - 8080:80

  # The Database
  database:
    image: daocloud.io/mysql:5.7.4
    volumes:
      - dbdata:/var/lib/mysql
    environment:
      - "MYSQL_DATABASE=homestead"
      - "MYSQL_USER=homestead"
      - "MYSQL_PASSWORD=secret"
      - "MYSQL_ROOT_PASSWORD=secret"
    ports:
        - "3306:3306"

  redis:
    image: daocloud.io/library/redis:4.0.10-alpine
    command: redis-server --appendonly yes

volumes:
  dbdata:

测试一遍

创建 Laravel 项目

composer create-project laravel/laravel demo

注:为了做测试,可以将 vendor 文件夹和 composer.lock 文件删除。

git clone

在 demo 项目相同文件夹下,git clone 我们自建的「laraveldocker」:
git clone https://github.com/fanly/laraveldocker.git

修改 docker-compose.yml

将 docker-compose.yml 文件的路径执行我们的项目:

app:
    build:
      context: ./
      dockerfile: app.dockerfile
    working_dir: /var/www
    volumes:
      - ../:/var/www

build

在 laraveldocker 下执行构建命令:

docker-compose up

未分类

整个速度还是蛮快的

未分类

接下来进入容器内

docker exec -it de075c525528 bash

我们看看安装插件的效果:

未分类

使用的是 https://packagist.laravel-china.org 国内镜像。

注:该镜像是由 Laravel China 社区联合 又拍云 与 优帆远扬 共同合作推出的公益项目,旨在为广大 PHP 用户提供稳定和高速的 Composer 国内镜像服务。

值得推荐使用

参考:laravel-china.org/topics/4484…

使用 yarn 或者 cnpm 安装插件:

未分类

生成 Laravel key secret:

cp .env.example .env
php artisan key:generate

Application key [base64:4A7VK6MEX7FakPLDSLji97kz/nyWUAWhW4wYn3gefsY=] set successfully.

运行下看看效果吧:

未分类

我们接下来看看数据库连接吧,修改 .env:

DB_CONNECTION=mysql
DB_HOST=database
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

我们使用 php artisan make:auth 来生成布局、注册和登录视图以及所有的认证接口的路由。同时它还会生成 HomeController 来处理应用的登录请求。使用 php artisan migrate 来载入数据。

未分类

我们看看数据表:

未分类

至此,说明我们连接 MySQL 数据库 OK.

总结

在学习过程中,使用别人做好的 Dockerfile,虽可以直接拿来使用,但如果能自给自足,那最好不过了。

通过自建 docker 开发环境过程中,也能让自己学到更多。接下来还会不断完善,最小化满足开发需要。

代码已放在 github 上,欢迎参考和提出 issue:

github.com/fanly/larav…

Docker部署Hadoop集群

一、主机规划

3台主机:1个master、2个slaver/worker
ip地址使用docker默认的分配地址:

master:
主机名: hadoop2、ip地址: 172.17.0.2

slaver1:
主机名: hadoop3、ip地址: 172.17.0.3
主机名: hadoop4、ip地址: 172.17.0.4

二、软件安装

1、在docker中安装centos镜像,并启动centos容器,安装ssh。–详见”docker上安装centos镜像”一文。
2、通过ssh连接到centos容器,安装jdk1.8、hadoop3.0
可以按照传统linux安装软件的方法,通过将jdk和hadoop的tar包上传到主机进行安装。

获取centos7镜像

$ docker pull centos

大概是70多M,使用阿里云等Docker加速器的话很快就能下载完,之后在镜像列表中就可以看到
查看镜像列表的命令:

$ docker images

安装SSH

以centos7镜像为基础,构建一个带有SSH功能的centos

$ vi Dockerfile

内容:

FROM centos
MAINTAINER [email protected]

RUN yum install -y openssh-server sudo
RUN sed -i 's/UsePAM yes/UsePAM no/g' /etc/ssh/sshd_config
RUN yum  install -y openssh-clients

RUN echo "root:abc123" | chpasswd
RUN echo "root   ALL=(ALL)       ALL" >> /etc/sudoers
RUN ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key
RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key

RUN mkdir /var/run/sshd
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]

这段内容的大意是:以 centos 镜像为基础,安装SSH的相关包,设置了root用户的密码为 abc123,并启动SSH服务
执行构建镜像的命令,新镜像命名为 centos7-ssh

$ docker build -t="centos7-ssh" .

执行完成后,可以在镜像列表中看到

$ docker images

构建Hadoop镜像

上面是运行了3个centos容器,需要在每个容器中单独安装Hadoop环境,我们可以像构建SSH镜像一样,构建一个Hadoop镜像,然后运行3个Hadoop容器,这样就更简单了

$ vi Dockerfile

内容:

FROM centos7-ssh
ADD jdk-8u151-linux-x64.tar.gz /usr/local/
RUN mv /usr/local/jdk1.8.0_151 /usr/local/jdk1.8
ENV JAVA_HOME /usr/local/jdk1.8
ENV PATH $JAVA_HOME/bin:$PATH

ADD hadoop-3.1.0.tar.gz /usr/local
RUN mv /usr/local/hadoop-3.1.0 /usr/local/hadoop
ENV HADOOP_HOME /usr/local/hadoop
ENV PATH $HADOOP_HOME/bin:$PATH

RUN yum install -y which sudo

这里是基于 centos7-ssh 这个镜像,把 JAVA 和 Hadoop 的环境都配置好了
前提:在Dockerfile所在目录下准备好 jdk-8u101-linux-x64.tar.gz 与 hadoop-2.7.3.tar.gz
执行构建命令,新镜像命名为 hadoop

$ docker build -t="hadoop" .

在/etc/hosts文件中添加3台主机的主机名和ip地址对应信息

172.17.0.2      hadoop2
172.17.0.3      hadoop3
172.17.0.4      hadoop4

在docker中直接修改/etc/hosts文件,在重启容器后会被重置、覆盖。因此需要通过容器启动脚本docker run的–add-host参数将主机和ip地址的对应关系传入,容器在启动后会写入hosts文件中。如:

docker run --name hadoop2--add-host hadoop2:172.17.0.2 --add-host hadoop3:172.17.0.3 --add-host hadoop4:172.17.0.4 hadoop
docker exec -it hadoop2 bash 
$ ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa
$ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
$ chmod 0600 ~/.ssh/authorized_keys

hadoop部署

1.在workers文件中定义工作节点
在hadoop根目录下的etc/hadoop目录下新建workers文件,并添加工作节点主机信息。
按照步骤一中的主机规划,工作节点主机为hadoop3和hadoop4两台主机。如:

[root@9e4ede92e7db ~]# cat /usr/local/hadoop/etc/hadoop/workers
hadoop3
hadoop4

2、修改配置文件信息

a、在hadoop-env.sh中,添加JAVA_HOME信息

[root@9e4ede92e7db ~]# cat /usr/local/hadoop/etc/hadoop/hadoop-env.sh |grep JAVA_HOME
#  JAVA_HOME=/usr/java/testing hdfs dfs -ls
# Technically, the only required environment variable is JAVA_HOME.
# export JAVA_HOME=
JAVA_HOME=/usr/local/jdk1.8

b、core-site.xml

configuration><property>
<name>fs.default.name</name>
<value>hdfs://hadoop2:9000</value>
</property>
<property>
<name>io.file.buffer.size</name>
<value>131072</value>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/home/hadoop/tmp</value>
<description>Abase for other temporary directories.</description>
</property>
</configuration>

c、hdfs-site.xml

<configuration>
<property>
<name>dfs.namenode.secondary.http-address</name>
<value>hadoop2:9001</value>
<description># 通过web界面来查看HDFS状态 </description>
</property>
<property>
<name>dfs.namenode.name.dir</name>
<value>/home/hadoop/dfs/name</value>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>/home/hadoop/dfs/data</value>
</property>
<property>
<name>dfs.replication</name>
<value>2</value>
<description># 每个Block有2个备份</description>
</property>
<property>
<name>dfs.webhdfs.enabled</name>
<value>true</value>
</property>
</configuration>

d、yarn-site.xml

<configuration>
<!-- Site specific YARN configuration properties -->
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<name>yarn.nodemanager.aux-services.mapreduce.shuffle.class</name>
<value>org.apache.hadoop.mapred.ShuffleHandler</value>
</property>
<property>
<name>yarn.resourcemanager.address</name>
<value>hadoop2:8032</value>
</property>
<property>
<name>yarn.resourcemanager.scheduler.address</name>
<value>hadoop2:8030</value>
</property>
<property>
<name>yarn.resourcemanager.resource-tracker.address</name>
<value>hadoop2:8031</value>
</property>
<property>
<name>yarn.resourcemanager.admin.address</name>
<value>hadoop2:8033</value>
</property>
<property>
<name>yarn.resourcemanager.webapp.address</name>
<value>hadoop2:8088</value>
</property>
<property>
<name>yarn.nodemanager.resource.memory-mb</name>
<value>1024</value>
</property>
<property>
<name>yarn.nodemanager.resource.cpu-vcores</name>
<value>1</value>
</property>
</configuration>

e、mapred-site.xml

<configuration>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<property>
<name>mapreduce.jobhistory.address</name>
<value>hadoop2:10020</value>
</property>
<property>
<name>mapreduce.jobhistory.webapp.address</name>
<value>hadoop2:19888</value>
</property>
</configuration>

f、为防止进坑提前做好准备

vi start-dfs.sh vi stop-dfs.sh

HDFS_DATANODE_USER=root
#HADOOP_SECURE_DN_USER=hdfs
HDFS_NAMENODE_USER=root
HDFS_SECONDARYNAMENODE_USER=root
HDFS_DATANODE_SECURE_USER=hdfs

vi start-yarn.sh vi stop-yarn.sh

YARN_RESOURCEMANAGER_USER=root
HADOOP_SECURE_DN_USER=yarn
YARN_NODEMANAGER_USER=root

注意:
以上步骤完成以后停止当前容器,并使用docker命令保持到一个新的镜像。使用新的镜像重新启动集群,这样集群每台机器都有相同的账户、配置和软件,无需再重新配置。如:

a、停止容器

docker stop hadoop2

b、保存镜像

docker commit hadoop2 hadoop_me:v1.0

测试
1、端口映射
集群启动后,需要通过web界面观察集群的运行情况,因此需要将容器的端口映射到宿主主机的端口上,可以通过docker run命令的-p选项完成。比如:
将yarn任务调度端口映射到宿主主机8088端口上:

docker run -it -p 8088:8088 hadoop_me:v1.0

2、从新镜像启动3个容器

docker run --name hadoop2 --add-host hadoop2:172.17.0.2 --add-host hadoop3:172.17.0.3 --add-host hadoop4:172.17.0.4 -d -p 5002:22 -p 9870:9870 -p 8088:8088 -p 19888:19888 
hadoop_me:v1.0

docker run --name hadoop3 --add-host hadoop2:172.17.0.2 --add-host hadoop3:172.17.0.3 --add-host hadoop4:172.17.0.4 -d -p 5003:22 hadoop_me:v1.0 

docker run --name hadoop4 --add-host hadoop2:172.17.0.2 --add-host hadoop3:172.17.0.3 --add-host hadoop4:172.17.0.4 -d -p 5004:22 hadoop_me:v1.0

3.格式化
进入到/usr/local/hadoop目录下
执行格式化命令

bin/hdfs namenode -format

修改hadoop2中hadoop的一个配置文件etc/hadoop/slaves
删除原来的所有内容,修改为如下

hadoop3
hadoop4

在hadoop2中执行命令

 scp  -rq /usr/local/hadoop   hadoop3:/usr/local
 scp  -rq /usr/local/hadoop   hadoop4:/usr/local

4.在master主机上执行start-all.sh脚本启动集群
5.通过web页面访问

未分类

未分类

未分类

Docker环境部署问题汇总

docker 相关命令:

  • docker ps: 查看当前运行的容器
  • docker ps -a : 查看所有的容器
  • docker start {容器id或name} 启动指定容器
  • docker stop {容器id或name} 停止指定容器
  • docker rm {容器id或name} 删除指定容器

具体docker相关使用指导参考:
http://kb.cnblogs.com/page/53…

运行docker容器,出现如下错误:

Cannot connect to the Docker daemon. Is the docker daemon running on this host?

解决方法参见下面链接:
http://blog.csdn.net/hejjiiee…

Ubutntu安装完Docker后,执行

$ sudo service docker start

提示

Failed to start docker.service: Unit docker.service is masked.

解决方案:

执行如下三条指令

$ systemctl unmask docker.service
$ systemctl unmask docker.socket
$ systemctl start docker.service
x Installing Docker: FAILED

-----------------------------------STDERR-----------------------------------
bash: line 4: docker: command not found

需要在服务器上先安装docker:

$ sudo apt-get install docker.io

普通用户不能执行docker命令:

修改 /etc/group

docker:xx:ubuntu

Centos 6.5 安装docker 问题:

  1. 安装教程参考: http://h2appy.blog.51cto.com/…
  2. 错误提示:
docker: relocation error: docker: symbol dm_task_get_info_with_deferred_remove, version Base not defined in file libdevmapper.so.1.02 with link time reference,

解决办法,执行:

$ yum upgrade device-mapper-libs

参考:http://blog.csdn.net/ownfire/…

docker 容器快照部署步骤:

1.将容器快照拷贝到本地
2.导入容器快照可以使用 docker import 从容器快照文件中再导入为镜像,例如

$ cat ubuntu.tar | sudo docker import - test/ubuntu:v1.0

3.根据导入的镜像创建容器(根据需求做必要的端口映射)

$ docker run -d --name shijiyu_live -p 80:80 -p 3306:3306 -p 6379:6379 -p 1935:1935 -p 8087:8087 -p 50001:22 xinjiguaike/lnmp-shijiyu:v3 /etc/rc.local
$ docker run -d --name centos_i386_auto --restart=always -p 3307:3307 -p 6680:6680 -p 6681:6681 -p 6680:6680/udp -p 6681:6681/udp -p 50003:22 shijiyu/centos_i386:v3 /etc/rc.local

关于docker 容器ssh连上就断开的问题:

Try to change

UsePAM yes

on

shell

UsePAM no

in /etc/ssh/sshd_config (for CentOS)

docker 容器目录结构:http://blog.csdn.net/wanglei_…

解决ssh登录后闲置时间过长而断开连接:

修改/etc/ssh/sshd_config配置文件,找到ClientAliveCountMax(单位为分钟)修改你想要的值,
执行

$ service sshd reload

ssh 密钥 无法用root用户登录:

将home/ubuntu/.ssh/authorized_keys 拷贝到 /root/.ssh 下

如何在Docker里正确集成Jenkins和Phabricator

|用Docker安装Jenkins非常简单,但要把一个运行在Docker里的Jenkins和Phabricator相集成,事情就变得不那么容易。

未分类

单独安装Jenkins并不复杂,用Docker安装Jenkins更加简单,甚至将Jenkins与Phabricator集成也不难,但要把一个运行在Docker里的Jenkins和Phabricator相集成,事情就变得不那么容易。

我把所有走过的坑全部隐藏,直接告诉你最正确的步骤。

这一步似乎很简单,但不要按照官方教程上所说的来,而按照以下命令执行:

docker run --name jenki
ns -p 8088:8080 -p 50000:50000 -v /var/jenkins_home:/var/jenkins_home -e PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/src/arcanist/bin' jenkins/jenkins

-p 8088:8080是为了避免8080端口冲突(毕竟只要是个Java程序就想占用8080端口),-e设置里特意增加了/src/arcanist/bin是为下一步集成Phabricator里的Arcanist做准备,因为如果不在这里设置好这个环境变量的话,后面会带来很大麻烦。

因为官方提供的Docker里根本就没有Arcanist,所以我们必须进入Docker的容器,手工安装arc。

docker exec -it -u root jenkins bash

好在这个Docker并不复杂,只是一个Debian,所以我们以root用户进入,然后:

apt-get update
apt-get install php
apt-get install php-curl
apt-get install rsync
apt-get install vim
mkdir /src
cd /src
git clone git://github.com/facebook/libphutil.git
git clone git://github.com/facebook/arcanist.git

把你的ssh公钥私钥文件拷到/var/jenkins_home/.ssh里,因为后面不论是git还是rsync你都需要它们。然后再以jenkins用户身份进入Docker:

docker exec -it -u jenkins jenkins bash

然后:

arc set-config default http://your.phabricator.com/
cd /var/jenkins_home/.ssh
chmod 600 id_rsa
chmod 600 id_rsa.pub

至此,你已经把官方提供的Docker改得面目全非,才算正确地在Docker里安装好了arc和Jenkins。

接下来的步骤,你就可以参照官方教程一步一步执行,我就不再重复了。

如果你也遇到了类似的头疼问题,希望这篇文章能够对你有所启发。

CentOS Docker 安装

Docker支持以下的CentOS版本:

CentOS 7 (64-bit)
CentOS 6.5 (64-bit) 或更高的版本

前提条件

目前,CentOS 仅发行版本中的内核支持 Docker。
Docker 运行在 CentOS 7 上,要求系统为64位、系统内核版本为 3.10 以上。
Docker 运行在 CentOS-6.5 或更高的版本的 CentOS 上,要求系统为64位、系统内核版本为 2.6.32-431 或者更高版本。

使用 yum 安装(CentOS 7下)

Docker 要求 CentOS 系统的内核版本高于 3.10 ,查看本页面的前提条件来验证你的CentOS 版本是否支持 Docker 。
通过 uname -r 命令查看你当前的内核版本

[root@runoob ~]# uname -r 3.10.0-327.el7.x86_64

未分类

安装 Docker

Docker 软件包和依赖包已经包含在默认的 CentOS-Extras 软件源里,安装命令如下:

[root@runoob ~]# yum -y install docker-io

未分类

安装完成。

未分类

启动 Docker 后台服务

[root@runoob ~]# service docker start

未分类

测试运行 hello-world

[root@runoob ~]#docker run hello-world

未分类

由于本地没有hello-world这个镜像,所以会下载一个hello-world的镜像,并在容器内运行。

使用脚本安装 Docker

1、使用 sudo 或 root 权限登录 Centos。
2、确保 yum 包更新到最新。

$ sudo yum update

3、执行 Docker 安装脚本。

$ curl -fsSL https://get.docker.com/ | sh

执行这个脚本会添加 docker.repo 源并安装 Docker。

4、启动 Docker 进程。

$ sudo service docker start

5、验证 docker 是否安装成功并在容器中执行一个测试的镜像。

$ sudo docker run hello-world
docker ps

到此,docker 在 CentOS 系统的安装完成。

镜像加速

鉴于国内网络问题,后续拉取 Docker 镜像十分缓慢,我们可以需要配置加速器来解决,我使用的是网易的镜像地址:http://hub-mirror.c.163.com。

新版的 Docker 使用 /etc/docker/daemon.json(Linux) 或者 %programdata%dockerconfigdaemon.json(Windows) 来配置 Daemon。

请在该配置文件中加入(没有该文件的话,请先建一个):

{
  "registry-mirrors": ["http://hub-mirror.c.163.com"]}

docker compose实战部署django

第一步,因为应用将要运行在一个满足所有环境依赖的 Docker 容器里面,那么我们可以通过编辑 Dockerfile 文件来指定 Docker 容器要安装内容。内容如下:

FROM python:3

ENV PYTHONUNBUFFERED 1

RUN mkdir /code

WORKDIR /code

ADD requirements.txt /code/

RUN pip install -r requirements.txt

ADD . /code/

第二步,在 requirements.txt 文件里面写明需要安装的具体依赖包名。

Django>=1.8,<2.0

psycopg2

第三步, docker-compose.yml 文件将把所有的东西关联起来。它描述了应用的构成(一个web 服务和一个数据库)、使用的 Docker 镜像、镜像之间的连接、挂载到容器的卷,以及服务开放的端口。

version: "3"

services:

    db:

        image: postgres

    web:

        build: .

        command: python3 manage.py runserver 0.0.0.0:8000

        volumes:

            – .:/code

        ports:

            – "8000:8000"

        links:

            – db

现在我们就可以使用 docker-compose run 命令启动一个 Django 应用了。

$ docker-compose run web django-admin.py startproject django_example .

Compose 会先使用 Dockerfile 为 web 服务创建一个镜像,接着使用这个镜像在容器里运行django-admin.py startproject composeexample 指令。这将在当前目录生成一个 Django 应用。

$ ls

Dockerfile docker-compose.yml django_example manage.py requ

irements.txt

如果你的系统是 Linux,记得更改文件权限。

sudo chown -R $USER:$USER .

首先,我们要为应用设置好数据库的连接信息。用以下内容替换 django_example/settings.py文件中 DATABASES = … 定义的节点内容。

DATABASES = {

    'default': {

        'ENGINE': 'django.db.backends.postgresql',

         'NAME': 'postgres',

         'USER': 'postgres',

         'HOST': 'db',

         'PORT': 5432,

    }

}

这些信息是在 postgres 镜像固定设置好的。然后,运行 docker-compose up :

$ docker-compose up

django_db_1 is up-to-date

Creating django_web_1 …

Creating django_web_1 … done

Attaching to django_db_1, django_web_1

db_1 | The files belonging to this database system will be owned by user "postgres".

db_1 | This user must also own the server process.

db_1 |

db_1 | The database cluster will be initialized with locale "en_US.utf8".

db_1 | The default database encoding has accordingly been set to "UTF8".

db_1 | The default text search configuration will be set to "english".

web_1 | Performing system checks…

web_1 |

web_1 | System check identified no issues (0 silenced).

web_1 |

web_1 | November 23, 2017 – 06:21:19

web_1 | Django version 1.11.7, using settings 'django_example.settings'

web_1 | Starting development server at http://0.0.0.0:8000/

web_1 | Quit the server with CONTROL-C.

这个 Django 应用已经开始在你的 Docker 守护进程里监听着 8000 端口了。打开127.0.0.1:8000 即可看到 Django 欢迎页面。

你还可以在 Docker 上运行其它的管理命令,例如对于同步数据库结构这种事,在运行完docker-compose up 后,在另外一个终端进入文件夹运行以下命令即可:

$ docker-compose run web python manage.py syncdb