二胖手

二胖手 查看完整档案

杭州编辑杭州电子科技大学  |  计算机科学 编辑  |  填写所在公司/组织 cycok.com 编辑
编辑

web development

个人动态

二胖手 赞了文章 · 2016-12-21

使用Docker+Nginx模拟负载均衡

原文发表于我的博客,转载请注明出处

一直听说Nginx的强大,它不仅可以作为Web服务器,按照调度规则实现动态、静态页面的分离;还可以作为反向代理服务器,构建服务集群,按轮询、权重等多种方式对后端服务器做负载均衡。以及自动剔除因故障负载均衡列表中宕机的服务器。这两天折腾了一下Nginx的安装、配置,并通过Docker模拟出Nginx在多服务器提供服务的状态下的负载均衡。

(一) 系统环境

操作系统DockerNginx
Ubuntu 161.12.11.8.0

(二) 准备Nginx环境

  使用Docker这种容器技术,可以很方便地将所需要的环境打包和快速部署。所以我将Nginx的环境做成Docker镜像,当需要多个Nginx服务时,只需要通过镜像启动多个容器。

  • 镜像已经PUSH到Docker Hub上,如果需要,可以直接在Docker拉取配置好nginx环境的镜像docker pull raomengnan/ubuntu:nginx-1.8.0

  • raomengnan/ubuntu:nginx-1.8.0 包含的基础环境: nginx,zsh,vim,ssh,python

Dockerfile

  # Ubuntu with Nginx
  # Author raomengnan
  FROM raomengnan/ubuntu-base
  MAINTAINER raomengnan

  # 安装升级gcc
  RUN rm -rf /var/lib/apt/lists/*
  RUN apt-get update

  # 添加相关的src
  RUN apt-get -y install build-essential
  RUN apt-get -y install supervisor
  RUN mkdir -p /usr/local/temp

  COPY supervisor.conf /etc/supervisor/conf.d/supervisord.conf

  RUN wget http://nginx.org/download/nginx-1.8.0.tar.gz && tar -zxvf nginx-1.8.0.tar.gz -C /usr/local/temp
  RUN wget http://zlib.net/zlib-1.2.8.tar.gz && tar -zxvf zlib-1.2.8.tar.gz -C /usr/local/temp
  RUN wget http://www.openssl.org/source/openssl-1.0.1q.tar.gz && tar -zxvf openssl-1.0.1q.tar.gz -C /usr/local/temp
  RUN wget http://downloads.sourceforge.net/project/pcre/pcre/8.37/pcre-8.37.tar.gz && tar -zxvf pcre-8.37.tar.gz -C /usr/local/temp

  RUN rm *.tar.gz

  # 安装
  RUN ls /usr/local/temp/nginx-1.8.0
  RUN cd /usr/local/temp/nginx-1.8.0 \
        && ./configure --sbin-path=/usr/local/nginx-1.8.0/nginx --conf-path=/usr/local/nginx-1.8.0/nginx.conf --pid-path=/usr/local/nginx-1.8.0/nginx.pid --with-http_ssl_module --with-pcre=/usr/local/temp/pcre-8.37 --with-zlib=/usr/local/temp/zlib-1.2.8 --with-openssl=/usr/local/temp/openssl-1.0.1q \
        && make \
        && make install

  # 设置nginx是非daemon启动,否则nginx无法启动
  RUN echo "\ndaemon off;" >> /usr/local/nginx-1.8.0/nginx.conf
  RUN echo 'master_process off;' >> /usr/local/nginx-1.8.0/nginx.conf
  RUN echo 'error_log  logs/error.log;' >> /usr/local/nginx-1.8.0/nginx.conf

  RUN rm -rf /usr/local/temp/*

  ENV NGINX_HOME /usr/local/nginx-1.8.0

  # 将nginx添加到command
  update-alternatives --install /usr/bin/nginx nginx /usr/local/nginx-1.8.0/nginx 300

  EXPOSE 80
  # 使用supervisor来管理多个进程同时启动
  # 若不想使用supervisor,可以使用:
  #   CMD nginx $/NGINX_HOME/nginx.conf
  #   或者进入容器手动启动nginx
  CMD ["/usr/bin/supervisord"]

supervisor.conf

[supervisord]
nodaemon=true

[program:sshd]
command=/usr/sbin/sshd -D

[program:nginx]
command=/usr/local/nginx-1.8.0/nginx -c /usr/local/nginx-1.8.0/nginx.conf

(三) 启动Nginx服务

启动nginx时,可以提供Nginx的welcome页面的访问服务,可以通过这个页面简单地尝试nginx提供的负载均衡。

通过镜像启动三个nginx服务

docker run --name ser1 -p 8881:80 raomengnan/ubuntu:nginx-1.8.0

docker run --name ser2 -p 8882:80 raomengnan/ubuntu:nginx-1.8.0

docker run --name ser3 -p 8883:80 raomengnan/ubuntu:nginx-1.8.0

-p参数将容器的80端口映射到宿主机的888×端口上

以ser1作为主服务器,进入容器内修改nginx配置文件

使用docker inspect ser1便可以看到容器的详细信息,其中注意NetworkSetting下的这一段信息就可以知道容器的网关和ip地址

"Gateway": "172.17.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.17.0.3",

docker exec -it ser1 bash进入容器内部
nginx安装目录在'/usr/local/nginx-1.8.0'下,vim /usr/local/nginx-1.8.0/nginx.conf编辑配置文件:

#user  nobody;
worker_processes  1;
events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    client_header_buffer_size    1k;
    large_client_header_buffers  4 4k;

    # 配置负载均衡,weight代表权重,权重越高,分配到的可能就越搭
    upstream 172.17.0.2 {

           server 172.17.0.2:8888 weight=5;
           server 172.17.0.3:80   weight=4;
           server 172.17.0.4:80   weight=3;
    }
    # 配置反向代理
    server {
           listen 80;
           server_name 172.17.0.2;
           location /{
              # 反向代理的主机头
              proxy_pass  http://172.17.0.2;
              proxy_set_header Host   $host;
              proxy_set_header   X-Real-IP        $remote_addr;
              proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
           }
    }

    # 侦听本地8888端口,以便为反向代理到本地的请求提供服务
    server {
           listen       8888;
           server_name  localhost;
           # 如过没有对代理的链接形式有特殊要求,可以直接将root和index写在server中
           location / {
               root   /home/html;
               index  index.html index.htm;
           }
          error_page   500 502 503 504  /50x.html;
          location = /50x.html {
                 root   /home/html;
         }
       }
}

在ser1这个容器中配置反向代理,要关注server 172.17.0.2:8888 weight=5;这一行,之所以反向代理到本机的8888端口,很好理解,因为若再次代理到80端口,永远不能代理到本机的服务中,陷入死循环。

以ser×作为服务器


#user  nobody;
worker_processes  1;
events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    client_header_buffer_size    1k;
    large_client_header_buffers  4 4k;

    server {
           listen       80;
           server_name  172.17.0.2;
           # 如过没有对代理的链接形式有特殊要求,可以直接将root和index写在server中
           location / {
               root   /home/html;
               index  index.html index.htm;
           }
          error_page   500 502 503 504  /50x.html;
          location = /50x.html {
                 root   /home/html;
         }
       }
}

serx和ser1的区别就在于http的设置,没有upstream,server_name 为要负载的服务器的ip。修改好配置文件后,使用nginx -s reload重新载入配置。

(四) 刷新网页测试

  • 修改三个ser中的index.html,方便观察

  • 在浏览器中打开 localhost:8881 或者 172.17.0.2,刷新网页

不断刷新,可以看到会打开不同容器中的index页面,说明服务被Nginx均衡地分配到不同的service上,这就是Nginx的负载均衡的作用。

查看原文

赞 7 收藏 31 评论 3

二胖手 赞了文章 · 2016-12-20

Docker 快速入门指引

本文最早发表于本人博客:Docker 快速入门指引

Docker是什么?

Docker是 Docker.Inc 公司开源的一个基于 LXC技术之上构建的Container容器引擎,基于Go语言并遵从Apache2.0协议开源。

开发者可以搭建他们的应用仅仅一次,就能保证让这个应用保持一致的跑在任何地方。运营人员可以将他们的服务器配置一遍,就能跑任何应用。

What is Docker’s architecture?

Docker uses a client-server architecture. The Docker client talks to the Dockerdaemon, which does the heavy lifting of building, running, and distributing your Docker containers. Both the Docker client and the daemon can run on the same system, or you can connect a Docker client to a remote Docker daemon. The Docker client and daemon communicate via sockets or through a RESTful API.

和传统虚拟主机的区别

传统虚拟机通过硬件虚拟化来创造一整个虚拟系统。每一个虚拟机内的应用不仅仅包含这个应用的一些类库代码,而且还包含一整个操作系统。

Docker所有的容器分享一个操作系统,他们显然会比虚拟机更小一些,使得他们可以存在100多个虚拟的系统在一个主机上(而不像一个严格限制数量的虚拟机)。

优点和特点

  • 标准化应用发布,docker容器包含了运行环境和可执行程序,可以跨平台和主机使用
  • 快速部署和启动,VM启动一般是分钟级,docker容器启动是秒级,即启即用
  • 方便构建基于SOA架构或微服务架构的系统,通过服务编排,更好的松耦合
  • 轻量低成本,占有更少的磁盘空间,一台主机可以启动上千个容器
  • 方便持续集成,通过与代码进行关联使持续集成非常方便
  • 安全隔离的执行环境,每个运行的容器互不影响

    • 文件系统分离,每一个进程容器跑在完全分离的root权限的文件系统下
    • 资源分离,系统资源(像CPU、内存)能被指定的分配给每一个进程容器,使用cgroups
    • 网络分离,使用一个虚拟的接口和IP地址,每一个进程容器跑在它自己的网络命名空间
  • 丰富的镜像资源,用户可以方便的在此基础上构建自己的容器运行
  • ......

一些概念和名称

  • Docker Client 是用户界面,它支持用户与Docker Daemon之间通信
  • Docker Daemon Docker最核心的后台进程,运行于主机上,处理服务请求
  • Docker Index是中央registry,支持拥有公有与私有访问权限的Docker容器镜像的备份
  • Docker Containers负责应用程序的运行,包括操作系统、用户添加的文件以及元数据
  • Docker Images是一个只读模板,用来运行Docker容器
  • DockerFile是文件指令集,用来说明如何自动创建Docker镜像

安装

  • 现在已经支持Windows,Mac OS X,以及大多数Linux版本 -> Install Docker Engine
  • DaoCloud的安装脚本
 #Ubuntu
  curl -sSL https://get.daocloud.io/docker | sh
  #CentOS
  curl -sSL https://get.daocloud.io/docker | sh
  sudo chkconfig docker on
  sudo systemctl start docker

仓库

仓库(Repository)是集中存放镜像文件的场所,仓库注册服务器(Registry)上往往存放着多个仓库,每个仓库中又包含了多个镜像,每个镜像有不同的标签(tag),和仓库(Repository)严格来讲不是同一个概念。
仓库分为公开仓库(Public)和私有仓库(Private),官方的 Docker Hub提供大量镜像提供下载,但是访问非常的慢,可以使用国内的公开仓库 时速云 、网易云阿里云 、DaoCloud加速器等
用户也可以在本地网络内创建一个私有仓库,创建了自己的镜像之后就可以使用 push 命令将它上传到公有或者私有仓库,其他机器上使用只需要从仓库上 pull 下来就可以了。

镜像

镜像就是一堆文件的集合,并不是像VM那样的是一个操作系统。镜像可以简单到只有一个程序文件。如果你写一个helloworld 静态编译后放到一个空的Image中,那么整个image的大小,就是你编译后的二进制文件的大小。一个ubuntu:14.04的镜像,提供了一个基本的ubuntu:14.04的发行版,镜像是不包含操作系统Linux内核。
如果在Debian镜像中安装MySQL 5.6,就成了mysql:5.6镜像。底层一个Debian操作系统镜像,上面叠加一个 MySQL层,就完成了一个MySQL镜像的构建。
Dockerfile构建出Docker镜像,通过Docker镜像运行Docker容器。Docker镜像是Docker容器运行的基础,没有Docker镜像,就不可能有Docker容器,这也是Docker的设计原则之一。

常用命令

  • docker images 显示本地已有镜像
  • docker info 显示docker系统信息
  • docker commit -m -a 提交更新后的镜像
  • docker build 通过Dockerfile来构建镜像
  • docker import 本地导入镜像
  • docker search 查找仓库中镜像
  • docker push 将镜像推送到仓库
  • docker pull 将仓库中镜像下载到本地
  • docker save -o mysql_5.6.tar mysql:5.6 导出镜像到本地
  • docker load < mysql_5.6.tar 载入镜像
  • docker rmi 移除镜像
  • docker attach 运行中容器的stdin,进行命令执行的动作
  • docker history 显示镜像的历史

Dockerfile

Dockerfile是Docker用来构建镜像的文本文件,包含自定义的指令和格式, 可以通过docker build命令从Dockerfile中构建镜像。

  • FROM
  FROM <image>[:<tag>]
  设置要制作的镜像基于哪个镜像,FROM指令必须是整个Dockerfile的第一个指令,如果指定的镜像不存在默认会自动从Docker Hub上下载。
  • MAINTAINER
  MAINTAINER <name>
  MAINTAINER指令允许你给将要制作的镜像设置作者信息
  • RUN
RUN <command>        #将会调用/bin/sh -c <command>
  RUN ["executable", "param1", "param2"]   #将会调用exec执行,以避免有些时候shell方式执行时的传递参数问题,而且有些基础镜像可能不包含/bin/sh
  RUN指令会在一个新的容器中执行任何命令,然后把执行后的改变提交到当前镜像,提交后的镜像会被用于Dockerfile中定义的下一步操作,RUN中定义的命令会按顺序执行并提交,这正是Docker廉价的提交和可以基于镜像的任何一个历史点创建容器的好处,就像版本控制工具一样。
  • CMD
  CMD ["executable", "param1", "param2"]    #将会调用exec执行,首选方式
  CMD ["param1", "param2"]        #当使用ENTRYPOINT指令时,为该指令传递默认参数
  CMD <command> [ <param1>|<param2> ]        #将会调用/bin/sh -c执行
  CMD指令中指定的命令会在镜像运行时执行,在Dockerfile中只能存在一个,如果使用了多个CMD指令,则只有最后一个CMD指令有效。
  当出现ENTRYPOINT指令时,CMD中定义的内容会作为ENTRYPOINT指令的默认参数,也就是说可以使用CMD指令给ENTRYPOINT传递参数。
  RUN和CMD都是执行命令,他们的差异在于RUN中定义的命令会在执行docker build命令创建镜像时执行,而CMD中定义的命令会在执行docker run命令运行镜像时执行,另外使用第一种语法也就是调用exec执行时,命令必须为绝对路径。
  • EXPOSE
  EXPOSE <port> [ ...]
  EXPOSE指令用来告诉Docker这个容器在运行时会监听哪些端口,Docker在连接不同的容器(使用–link参数)时使用这些信息。
  • ENV
  ENV <key> <value>
  ENV指令用于设置环境变量,在Dockerfile中这些设置的环境变量也会影响到RUN指令,当运行生成的镜像时这些环境变量依然有效,如果需要在运行时更改这些环境变量可以在运行docker run时添加–env <key>=<value>参数来修改。
  最好不要定义那些可能和系统预定义的环境变量冲突的名字,否则可能会产生意想不到的结果。
  • ADD
  ADD <src> <dest>
  ADD指令用于从指定路径拷贝一个文件或目录到容器的指定路径中,<src>是一个文件或目录的路径,也可以是一个url,路径是相对于该Dockerfile文件所在位置的相对路径,<dest>是目标容器的一个绝对路径
  例如/home/yooke/Docker/Dockerfile这个文件中定义的,那么ADD /data.txt /db/指令将会尝试拷贝文件从/home/yooke/Docker/data.txt到将要生成的容器的/db/data.txt,且文件或目录的属组和属主分别为uid和gid为0的用户和组,如果是通过url方式获取的文件,则权限是600。
  注意: ①如果执行docker build – < somefile即通过标准输入来创建时,ADD指令只支持url方式,另外如果url需要认证,则可以通过RUN wget …或RUN curl …来完成,ADD指令不支持认证。
        ②<src>路径必须与Dockerfile在同级目录或子目录中,例如不能使用ADD ../somepath,因为在执行docker build时首先做的就是把Dockerfile所在目录包含子目录发送给docker的守护进程。
        ③如果<src>是一个url且<dest>不是以”/“结尾,则会下载文件并重命名为<dest>。
        ④如果<src>是一个url且<dest>以“/”结尾,则会下载文件到<dest>/<filename>,url必须是一个正常的路径形式,“http://example.com”像这样的url是不能正常工作的。
        ⑤如果<src>是一个本地的压缩包且<dest>是以“/”结尾的目录,则会调用“tar -x”命令解压缩,如果<dest>有同名文件则覆盖,但<src>是一个url时不会执行解压缩。
  • COPY
  COPY <src> <dest>
  用法与ADD相同,<src>不支持使用url
  • ENTRYPOINT
  ENTRYPOINT ["executable", "param1", "param2"]        #将会调用exec执行,首选方式
  ENTRYPOINT command param1 param2             #将会调用/bin/sh -c执行
  ENTRYPOINT指令中指定的命令会在镜像运行时执行,在Dockerfile中只能存在一个,如果使用了多个则只有最后一个指令有效。
  ENTRYPOINT指令中指定的命令(exec执行的方式)可以通过docker run来传递参数,例如docker run <images> -l启动的容器将会把-l参数传递给ENTRYPOINT指令定义的命令并会覆盖CMD指令中定义的默认参数(如果有的话),但不会覆盖该指令定义的参数,例如ENTRYPOINT ["ls","-a"],CMD ["/etc"],当通过docker run <image>启动容器时该容器会运行ls -a /etc命令,当使用docker run <image> -l启动时该容器会运行ls -a -l命令,-l参数会覆盖CMD指令中定义的/etc参数。
  当使用ENTRYPOINT指令时生成的镜像运行时只会执行该指令指定的命令。
  当出现ENTRYPOINT指令时CMD指令只可能(当ENTRYPOINT指令使用exec方式执行时)被当做ENTRYPOINT指令的参数使用,其他情况则会被忽略。
  • VOLUME
  VOLUME ["samepath"]
  VOLUME指令用来设置一个挂载点,可以用来让其他容器挂载以实现数据共享或对容器数据的备份、恢复或迁移。
  • USER
  USER [username|uid]
  USER指令用于设置用户或uid来运行生成的镜像和执行RUN指令。
  • WORKDIR
  WORKDIR /path/to/workdir
  WORKDIR指令用于设置Dockerfile中的RUN、CMD和ENTRYPOINT指令执行命令的工作目录(默认为/目录),该指令在Dockerfile文件中可以出现多次,如果使用相对路径则为相对于WORKDIR上一次的值,例如WORKDIR /data,WORKDIR logs,RUN pwd最终输出的当前目录是/data/logs。
  • ONBUILD
  ONBUILD [INSTRUCTION]
  ONBUILD指令用来设置一些触发的指令,用于在当该镜像被作为基础镜像来创建其他镜像时(也就是Dockerfile中的FROM为当前镜像时)执行一些操作,ONBUILD中定义的指令会在用于生成其他镜像的Dockerfile文件的FROM指令之后被执行,上述介绍的任何一个指令都可以用于ONBUILD指令,可以用来执行一些因为环境而变化的操作,使镜像更加通用。
  注意: ①ONBUILD中定义的指令在当前镜像的build中不会被执行。
        ②可以通过查看docker inspeat <image>命令执行结果的OnBuild键来查看某个镜像ONBUILD指令定义的内容。
        ③ONBUILD中定义的指令会当做引用该镜像的Dockerfile文件的FROM指令的一部分来执行,执行顺序会按ONBUILD定义的先后顺序执行,如果ONBUILD中定义的任何一个指令运行失败,则会使FROM指令中断并导致整个build失败,当所有的ONBUILD中定义的指令成功完成后,会按正常顺序继续执行build。
        ④ONBUILD中定义的指令不会继承到当前引用的镜像中,也就是当引用ONBUILD的镜像创建完成后将会清除所有引用的ONBUILD指令。
        ⑤ONBUILD指令不允许嵌套,例如ONBUILD ONBUILD ADD . /data是不允许的。
        ⑥ONBUILD指令不会执行其定义的FROM或MAINTAINER指令。

容器

容器 = 镜像 + 可读层
Docker容器就像是一个文件夹,它包含了一个应用程序运行所需要的所有内容。每个容器都是基于Docker镜像构建。我们可以运行、开始、停止、迁移或者是删除Docker容器。每个容器均是一个隔离的、安全的应用平台。Docker容器是Docker的运行组件。

常用命令

  • docker run 新建并启动容器

    • -d 容器运行在后台,此时不能使用--rm选项
    • -i -t 和容器进行交互式操作
    • --name 命名容器,没有该参数Docker deamon会生产UUID来标识
    • --cidfile 将容器ID输入到指定文件中
    • --add-host 添加一行到/etc/hosts
    • --mac-address 设置MAC地址
    • --dns 覆盖容器DNS设置
    • --rm 退出容器时自动清除数据
    • -m 调整容器的内存使用
    • -c 调整容器的CPU优先级
    • -e 设定环境变量
    • --expose 运行时暴露端口,不创建和宿主机的映射
    • -p 创建映射规则,将一个或者一组端口从容器里绑定到宿主机上,可多次使用

      • ip:hostPort:containerPort
      •  ip::containerPort  
      •  hostPort:containerPort  
      •  containerPort
    • -P 将Dockfile中暴露的端口映射动态映射到宿主机
    • --link 容器互联 --link name:alias
    • -v 创建数据卷挂载到容器,一次run中可多次使用
    • 可覆盖Dockfile参数

      • CMD
      • ENTRYPOINT
      • EXPOSE
      • ENV
      • VOLUME
      • USER
      • WORKDIR
  • docker stop 停止运行中容器
  • docker stop $(docker ps -qa) 停止所有运行中的容器
  • docker restart 重启容器
  • docker ps -a 查看所有容器
  • docker rm 移除处于终止状态的容器

    • docker rm $(docker ps -qa) 移除处于终止状态的容器
  • docker logs 从容器中去日志
  • docker diff 列出容器中被改变的文件或者目录
  • docker top 显示运行容器的进程信息
  • docker cp 从容器中拷贝文件或者目录到本地
  • docker inspect 查看容器详细信息

Volume和Volume Containers

数据卷类似于 Linux 下对目录或文件进行 mount,本质是容器中一个特殊的文件或目录(挂载点)。在容器的创建过程中,这个挂载点会被挂载一个宿主机上的指定的目录 (一个以volumeID为名称的目录 或者指定的宿主机目录)。它的设计用来持久化数据的,生命周期独立于容器。

  • docker run -i -t -v /data debian:jessie /bin/sh
    将宿主的/var/lib/docker/volumes/volume_id/_data 绑定挂载到 /data
  • docker run -i -t -v /var/www/:/data debian:jessie /bin/sh
    ​将宿主机的/var/www/ 绑定挂载到 /data
  • docker run -i -t -v /var/www/:/data:ro debian:jessie /bin/sh
    ​挂载为只读

数据卷容器,用来提供数据卷供其它容器挂载的正常容器。

  • 创建一个数据卷容器dbdata
  docker run -d -v /var/www/:/dbdata --name dbdata debian:jessie
  • 其他容器挂载 dbdata容器中的数据卷
  docker run -d --volumes-from dbdata --name db1 debian:jessie
  • 也可以从其他已经挂载了数据卷的容器来级联挂载数据卷
  docker run -d --name db2 --volumes-from db1 debian:jessie

如果删除了挂载的容器,数据卷并不会被自动删除。如果要删除一个数据卷,必须在删除最后一个还挂载着它的容器时使用 docker rm -v 命令来指定同时删除关联的容器。

  • 备份数据卷,使用了 tar 命令来将 dbdata 卷备份为容器中 /backup/backup.tar 文件,也就是主机当前目录下的名为 backup.tar 的文件。
  docker run --volumes-from dbdata -v $(pwd):/backup debian:jessie tar cvf /backup/backup.tar /dbdata
  • 恢复数据卷
  # 创建一个带有空数据卷的容器
  docker run -v /dbdata --name dbdata2 debian:jessie /bin/bash
  # 创建另一个容器,挂载 dbdata2 容器卷中的数据卷,并解压备份文件到挂载的容器卷中。
  docker run --volumes-from dbdata2 -v $(pwd):/backup debian:jessie tar xvf /backup/backup.tar
  # 再启动一个容器挂载同样的容器卷来查看恢复的数据
  docker run --volumes-from dbdata2 debian:jessie /bin/ls /dbdata

参考

Docker Docs

Docker入门与实战

如果觉得本篇文章对您十分有益,何不 打赏一下

谢谢打赏

谢谢打赏

查看原文

赞 3 收藏 10 评论 0

二胖手 发布了文章 · 2016-10-19

Redux使用探究

动机

如今用React或者vue开发单页应用越来越普遍。当项目越来越大,管理不断变化的state越来越困难。可能散落在各处。需要一个统一的容器来管理各种state
ReduxJavaScript 状态容器。 它和reactvue无直接关系。只是刚好两者可以结合起来发挥不错的。

三大原则

Redux 可以用这三个基本原则来描述:

单一数据源

整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store

State 是只读的

惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。

使用纯函数来执行修改

为了描述 action 如何改变 state tree ,你需要编写 reducers

Reducer 只是一些纯函数,它接收先前的 stateaction,并返回新的 state。刚开始你可以只有一个 reducer,随着应用变大,你可以把它拆成多个小的 reducers,分别独立地操作 state tree 的不同部分,因为 reducer 只是函数,你可以控制它们被调用的顺序,传入附加数据,甚至编写可复用的 reducer 来处理一些通用任务,如分页器。

API 文档

Redux 的 API 非常少。

记住,Redux 只关心如何管理 state。在实际的项目中,你还需要使用 UI 绑定库如 react-redux。实际上就是帮你处理了state变化时的监听

createStore

产生store

combineReducers

把多个小reducers组合成一个reducer

applyMiddleware(...middlewares)

action中间件

bindActionCreators

产生可以不用dispatch的action (感觉用处并不大)

compose

把多个中间件组合在一起

查看原文

赞 0 收藏 2 评论 0

二胖手 关注了问题 · 2016-08-01

解决如何设计 webpack + vue 模式的架构?

使用webpack + vue搭建一个新系统,主要有这几个问题:

  1. 如何区分开发环境与生产环境,不同环境使用不同的常量,并且可以在各个组件的template中直接使用,比如生产环境下的静态服务器地址与开发环境不同,是否是debug模式等。我查到vue有全局的filter和component,没有全局的data,但有个mixin,但官方又不推荐使用

  2. 如何定义全局变量,比如用于储存登录状态,用户行为记录等

  3. 引用了一个弹出框的组件,但貌似并不能直接访问该组件上的方法,应该通过什么方式与该组件通信,使用broadcast?

第一次搭webpack环境,不知道怎样设计架构比较成熟稳定。

如果大家有这方面丰富的开发经验,也很愿意倾听大家的建议和分享。

关注 7 回答 4

二胖手 回答了问题 · 2016-08-01

解决如何设计 webpack + vue 模式的架构?

我已经配好了, ESlint, SASS, AUTOFIXED等等什么的 可以参考 web-style

关注 7 回答 4

二胖手 发布了文章 · 2016-07-21

web-style文档

下载

npm

Web-Style 推荐使用 npm 下载或者访问 Web-Style

$ npm install web-style  --save

内容说明

编译文件

Web-Style 是通过 webpack 进行打包编译。采用了 ESLint 规范。编译文件都在dist目录下, 目录结构:

dist/
├── css/
│   └─ web-style.css
├── js/
│   └─ web-style.js
└── fonts/
    ├─ iconfont.eot
    ├─ iconfont.svg
    ├─ iconfont.ttf
    └─ iconfont.woff             

上面展示的就是Web-Style编译文件, 编译文件可以直接使用到任何web项目中. 提供了编译好的css文件, 包括基础样式和组件的样式。提供了编译好的vue组件 同时还提供了一套 iconfont 的图标字体

源文件

Web-Style 源文件目录结构:

src/
├── assets/
│   ├─ sass/
│   └─ fonts/
└── components/

assets 下存放公共的样式和字体文件, 采用 sass 预编译 以及采用 autoprefixer 添加兼容性的前缀

使用方法

ES6

import Vue from 'vue'
import { Message } from 'Web-style'

new Vue({
    el: 'body',
    components:{
       'v-message': Message
    }
})

浏览器使用

Web-Style 依赖 vue , 必须在 web-style前引入.

<script data-original="path/to/vue.js"></script>
<script data-original="path/to/web-style.js"></script>
<script>
    var app = new Vue({
        el: "body",
        components: {
            alert: VueStrap.alert
        }
    })
</script>

编译CSS 和 JavaScript 文件

Web-Style 使用 webpack 作为编译系统, 并且对外提供了一些方便的方法用于编译整个框架。

安装依赖

推荐使用 cnpm 安装依赖

$ cnpm install

打包公共的样式

$ npm run static

该指令执行后会把 assets/ 下的文件打包到 static/ 这样做的目的是在开发的时候无需重复打包公共部分, 提升编译效率。一旦修改assets/ 一定要第执行该指令

开发模式

$ npm run dev

本地开启一个服务器, 自动打开浏览器访问index.html文件, 进行开发vue组件

打包文件

$ npm run build

会打包所有组件和公共的样式生成到dist目录下。

查看原文

赞 0 收藏 2 评论 0

二胖手 赞了回答 · 2016-07-01

解决淘宝首页的图片无缝轮播是如何实现的

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>焦点轮播图</title>
    <style type="text/css">
        *{ margin: 0; padding: 0; text-decoration: none;}
        body { padding: 20px;}
        #container { width: 600px; height: 400px; border: 3px solid #333; overflow: hidden; position: relative;}
        #list { width: 4200px; height: 400px; position: absolute; z-index: 1;}
        #list img { float: left;}
        #buttons { position: absolute; height: 10px; width: 100px; z-index: 2; bottom: 20px; left: 250px;}
        #buttons span { cursor: pointer; float: left; border: 1px solid #fff; width: 10px; height: 10px; border-radius: 50%; background: #333; margin-right: 5px;}
        #buttons .on {  background: orangered;}
        .arrow { cursor: pointer; display: none; line-height: 39px; text-align: center; font-size: 36px; font-weight: bold; width: 40px; height: 40px;  position: absolute; z-index: 2; top: 180px; background-color: RGBA(0,0,0,.3); color: #fff;}
        .arrow:hover { background-color: RGBA(0,0,0,.7);}
        #container:hover .arrow { display: block;}
        #prev { left: 20px;}
        #next { right: 20px;}
    </style>
    <script type="text/javascript">

        window.onload = function () {
            var container = document.getElementById('container');
            var list = document.getElementById('list');
            var buttons = document.getElementById('buttons').getElementsByTagName('span');
            var prev = document.getElementById('prev');
            var next = document.getElementById('next');
            var index = 1;
            var len = 5;
            var animated = false;
            var interval = 3000;
            var timer;


            function animate (offset) {
                if (offset == 0) {
                    return;
                }
                animated = true;
                var time = 300;
                var inteval = 10;
                var speed = offset/(time/inteval);
                var left = parseInt(list.style.left) + offset;

                var go = function (){
                    if ( (speed > 0 && parseInt(list.style.left) < left) || (speed < 0 && parseInt(list.style.left) > left)) {
                        list.style.left = parseInt(list.style.left) + speed + 'px';
                        setTimeout(go, inteval);
                    }
                    else {
                        list.style.left = left + 'px';
                        if(left>-200){
                            list.style.left = -600 * len + 'px';
                        }
                        if(left<(-600 * len)) {
                            list.style.left = '-600px';
                        }
                        animated = false;
                    }
                }
                go();
            }

            function showButton() {
                for (var i = 0; i < buttons.length ; i++) {
                    if( buttons[i].className == 'on'){
                        buttons[i].className = '';
                        break;
                    }
                }
                buttons[index - 1].className = 'on';
            }

            function play() {
                timer = setTimeout(function () {
                    next.onclick();
                    play();
                }, interval);
            }
            function stop() {
                clearTimeout(timer);
            }

            next.onclick = function () {
                if (animated) {
                    return;
                }
                if (index == 5) {
                    index = 1;
                }
                else {
                    index += 1;
                }
                animate(-600);
                showButton();
            }
            prev.onclick = function () {
                if (animated) {
                    return;
                }
                if (index == 1) {
                    index = 5;
                }
                else {
                    index -= 1;
                }
                animate(600);
                showButton();
            }

            for (var i = 0; i < buttons.length; i++) {
                buttons[i].onclick = function () {
                    if (animated) {
                        return;
                    }
                    if(this.className == 'on') {
                        return;
                    }
                    var myIndex = parseInt(this.getAttribute('index'));
                    var offset = -600 * (myIndex - index);

                    animate(offset);
                    index = myIndex;
                    showButton();
                }
            }

            container.onmouseover = stop;
            container.onmouseout = play;

            play();

        }
    </script>
</head>
<body>

<div id="container">
    <div id="list" style="left: -600px;">
        <img data-original="img/5.jpg" alt="1"/>
        <img data-original="img/1.jpg" alt="1"/>
        <img data-original="img/2.jpg" alt="2"/>
        <img data-original="img/3.jpg" alt="3"/>
        <img data-original="img/4.jpg" alt="4"/>
        <img data-original="img/5.jpg" alt="5"/>
        <img data-original="img/1.jpg" alt="5"/>
    </div>
    <div id="buttons">
        <span index="1" class="on"></span>
        <span index="2"></span>
        <span index="3"></span>
        <span index="4"></span>
        <span index="5"></span>
    </div>
    <a href="javascript:;" id="prev" class="arrow">&lt;</a>
    <a href="javascript:;" id="next" class="arrow">&gt;</a>
</div>

</body>
</html>

关注 14 回答 7

二胖手 回答了问题 · 2016-06-25

解决关于页面过渡的效果。

css可以写 transition属性即可

关注 6 回答 4

二胖手 回答了问题 · 2016-06-20

解决移动端像左右滑动元素的效果怎样不出现滚动条?

iScroll.js

关注 8 回答 5

二胖手 赞了文章 · 2016-06-19

服务器搭建-Linux基础知识

服务器搭建还是需要一些Linux知识的,这节就聊点基础的。

文件权限操作

查看权限

Linux中每个文件对每个用户来说都有对应的权限,在任一路径中输入ll就可以查看这些信息:

root@ip-*** /usr/local # ll
total 32K
drwxr-xr-x 2 root root 4.0K Jan 14 17:00 bin
drwxr-xr-x 2 root root 4.0K Jan 14 17:00 etc
drwxr-xr-x 2 root root 4.0K Jan 14 17:00 games
drwxr-xr-x 2 root root 4.0K Jan 14 17:00 include
drwxr-xr-x 4 root root 4.0K Jan 14 17:08 lib
lrwxrwxrwx 1 root root    9 Jan 14 17:00 man -> share/man
drwxr-xr-x 2 root root 4.0K Jan 14 17:00 sbin
drwxr-xr-x 7 root root 4.0K May 27 14:02 share
drwxr-xr-x 2 root root 4.0K Jan 14 17:00 src

关于权限方面我们主要关注第1、3、4列
我们先来看第一列,第一列表示各用户对该文件/目录的操作权限,我把它断成四个部分,d|rwx|r-x|r-x分别表示类型|文件所有者权限|文件所属用户组权限|其他人的权限。类型我们经常用到的有d-目录--文件l-连接文件r(4)|w(2)|x(1)分别对应读|写|可执行(进入目录)
第三列和第四列分别代表文件所有者和文件所属用户组。

改变权限属性

改变文件权限需要用到chmod命令,上面我在rwx中标注了4、2、1,代表的就是它们的对应值,需要哪些权限把它们相加就是了,来个示例:

root@ip-*** ~ # ll  
total 4.0K
-rw-r--r-- 1 root root 25 Jun  8 02:33 dev.js

计算一下得到文件dev.js对各用户的权限值是644,下面使用chmod命令改变一下:

root@ip-*** ~ # chmod 761 dev.js 
root@ip-*** ~ # ll
total 4.0K
-rwxrw---x 1 root root 25 Jun  8 02:33 dev.js

这里分别赋予文件所有者|文件所属用户组|其他用户的权限是可读可写可执行|可读可写|可执行


另外,除了更改文件权限之外,还可以更改文件所有者和所在用户组,分别用到的命令是chownchgrp,具体使用方式可以使用man命令查看。

磁盘

一般在我们购买服务器的时候我们都会选择安装好一个Linux系统,分区啥的都已经配置好了,这方面对入门同学来讲并不需要掌握太多,主要注重查看。
查看磁盘使用df命令,加上参数-h使用以人类易读的方式展示。

压缩与打包

Linux中有很多软件是下载一个压缩包让用户自己解压安装的,这就需要我们掌握必要的压缩技能。使用比较广泛的有gzipbzip2tar

gzip、zcat

gzip应该说是目前Linux系统中使用最广的压缩命令了。它可以解开*.z*.zip*.gz等格式的压缩文件,下面来看它的使用方式:

root@ip-*** ~ # gzip -v dev.js 
dev.js:     -8.0% -- replaced with dev.js.gz
root@ip-*** ~ # ls
dev.js.gz

这里的-v是为了在压缩结束后查看压缩比,当然还有其他参数:

-c 将压缩的数据输出
-d 解压缩
-t 校验压缩文件
-1~-9 设置压缩级别,-1最快,压缩效果也最差,-9最慢,压缩效果最好,默认-6,一般情况下使用默认的就可以了。

so,当我们需要解压缩文件的时候只要加上-d参数就可以了。
我们知道cat命令可以用来查看纯文本文件,那zcat就是用来查看纯文本被压缩后的压缩文件。
另外,需要注意的一点是使用gzip压缩后源文件就会被自动删除。

bzip2、bzcat

bzip2是比gzip更优秀的压缩工具,用法跟gzip非常近似:

-c 将压缩的数据输出
-d 解压缩
-k 保留原文件
-z 压缩参数
-t 校验压缩文件
-1~-9 设置压缩级别,-1最快,压缩效果也最差,-9最慢,压缩效果最好。

其压缩文件以*.bz2格式存在,bzcat的作用与zcat相似,不再赘述。

tar

上面我们讲到了压缩命令,但是这里的压缩都是单文件的,要是我想把多个文件压缩在同一个压缩包中光用上面的命令是做不到的,有的同学会说我们可以压缩一个文件夹啊,想法很好,但是不好意思,你这么做的话会类似这样的提示:* is a directory -- ignored。所以这里我们要用到另一个工具tar,把那些文件打包成一个文件合集再使用压缩工具进行压缩。
值得一提的是,tar还可以同时在参数中直接使用gzip/bzip2tar的命令非常多,介绍几个常用的:

-c 新建打包文件
-j/-z 使用bzip2/gzip压缩,打包文件名通常应该是*.tar.bz2/*.tar.gz
-t 查看打包文件内容,主要用来查看文件名
-x 解压缩,通常搭配 -C 用于解压到目标目录
-v 查看压缩/解压过程中,正在被处理的文件
-C 解压到指定目录,与 -x 一起使用
-f 后面接被处理的文件名

下面是几个常用的组合,一般情况下足以应付大部分使用场景了。

压缩(bzip2):tar -jcv -f target.tar.bz2 待压缩的目录
压缩(gzip):tar -zcv -f target.tar.gz 待压缩的目录
查看包含文件(bzip2):tar -jt -f target.tar.gz/target.tar.bz2
查看包含文件(gzip):tar -zt -f target.tar.gz/target.tar.gz
解压(bzip2):tar -jxv -f target.tar.bz2 -C 解压到某处
解压(gzip):tar -zxv -f target.tar.gz -C 解压到某处

来个实例:

root@ip-*** ~ # ls -a
.                        .config     .zcompdump-ip-172-31-17-254-5.0.2
..                       .oh-my-zsh  .zsh-update
.aptitude                .profile    .zsh_history
.bash_history            .ssh        .zshrc
.bashrc                  .viminfo    dev.js
.cloud-locale-test.skip  .zcompdump  test

root@ip-*** ~ # tar -jcv -f test.tar.bz2 .
...
./.oh-my-zsh/lib/completion.zsh
./.oh-my-zsh/lib/diagnostics.zsh
./.profile
./.zsh_history
./.cloud-locale-test.skip
./.bashrc

root@ip-*** ~ # ls -a
.                        .oh-my-zsh                         .zsh_history
..                       .profile                           .zshrc
.aptitude                .ssh                               dev.js
.bash_history            .viminfo                           test
.bashrc                  .zcompdump                         test.tar.bz2
.cloud-locale-test.skip  .zcompdump-ip-172-31-17-254-5.0.2
.config                  .zsh-update

打包压缩成功后,可以看到最后多了一个test.tar.bz2文件。另外,另外两组组合可以自己尝试一下,另外,解压强烈建议加上-C命令,以免覆盖原来的文件。

shell脚本

shell脚本是能够在shell环境中执行的程序,它遵从shell语法,有了它,我们可以为一些需要重复操作的系列连续性命令做一个合集,解放劳动力。
下面我先给个常用的git案例:

#!/bin/bash    //声明使用的shell环境
action="none"    //变量赋值
echo "1: commit to dev and update test"    //打印字符串
echo "2: merge branch to master"
read -p "please input number to select next step: " -t 30 action    //读取用户键盘输入并赋给 action 变量,30秒内无输入则跳过

case $action in    //case语句,判断条件为 action 变量
1)
    read -p "please input commit content: " -t 30 commit
    if [ "$commit" == "" ]; then    //if判断语法
        commit="default commit"    //条件符合执行的操作
    fi    //if判断语句结束
    git add .
    git commit -m "$commit"    //引用变量
    git push origin dev
    exit 0    //退出shell
    ;;    //满足 action 为1的条件的语句结束
2)
    echo "1: cyc"
    echo "2: bingqichen"
    echo "3: dev"
    branch[1]="cyc"    //数组赋值
    branch[2]="bingqichen"
    branch[3]="dev"
    read -p "please input number to select the branch: " -t 30 branch_no
    if [ "$branch_no" == "1" ] || [ "$branch_no" == "2" ] || [ "$branch_no" == "3" ]; then    //if条件判断,|| 表示或关系
        git checkout master
        git merge "${branch[$branch_no]}"    //${branch[1]}为数组的引值方法
        git push origin master
        git checkout -
    else    //另外还有 elif ,跟js中的 else if 类似,可以继续添加判断条件
        echo "sorry, this is an undefined branch!"
    fi
    exit 0
    ;;
*)    //action为其他值的操作
    echo "sorry, this is an undefined action!"
    exit 0
    ;;
esac    //case语句结束

这段脚本就是我用来解决git提交和代码合并的问题,大家可以根据自己的使用场景改造。大部分语句还是很简单的,主要有几个需要注意的点,比如条件判断语句:

[ "$branch_no" == "1" ]

这里面的每个空格都不可以省略!还有赋值语句不能加空格,慎用单引号等等。

编译安装

Linux中安装软件的方法主要分为使用包管理器安装,和下载源代码自行编译安装,前者相对简单,但是有可能有些最新的软件包未必会有,比如PHP7,这时候就需要我们下载软件源码编译成二进制文件才能使用。
先来看看编译过程:
编译过程
大致过程是这样的,当我们下载到软件源码后,一般会是一个压缩包,解压之后里面会有一个用于建立Makefile文件的config/configure文件,使用config/configure建立Makefile后,执行make命令将源代码根据Makefile文件的配置进行编译,最后执行make install命令,将编译好的内容依据Makefileinstall选项安装到指定位置完成安装。
这里讲讲为什么要根据Makefile进行编译,其实GCC可以直接编译程序文件,但是一个软件不可能只拥有一个程序文件,要是人工一个个去编译每个文件,会非常劳民伤财!所以借助一个Makefile我们就可以搞定了。
下面来演示一个PHP7的编译安装:

root@ip-*** ~ # wget http://cn2.php.net/get/php-7.0.0.tar.gz/from/this/mirror    //下载源码
root@ip-*** ~ # ls
mirror
root@ip-*** ~ # mv mirror php.tar.gz    //重命名
root@ip-*** ~ # tar -zxv -f php.tar.gz    //解压缩源码包
root@ip-*** ~ # ls 
php-7.0.0  php.tar.gz
root@ip-*** ~ # cd php-7.0.0    //进入源文件目录
root@ip-*** ~/php-7.0.0 # ls    //里面会有一些手册,可以用来做安装参考
INSTALL            README.md            configure
...
root@ip-*** ~/php-7.0.0 # ./configure    //执行configure建立Makefile
checking for grep that handles long lines and -e... /bin/grep
checking for egrep... /bin/grep -E
...
checking for xml2-config path... 
configure: error: xml2-config not found. Please check your libxml2 installation.

我在编译到这一步报了一个错误,看起来是由于libxml2未安装,在我尝试安装后发现已经存在这个软件,在网上搜寻一片后发现还要安装libxml2-dev,这个我不是很懂。。。再次执行./configure成功并自动建立了Makefile文件,继续:

root@ip-*** ~/php-7.0.0 # make    //这个步骤视软件大小和硬件性能执行时间会有差异
/bin/bash /root/php-7.0.0/libtool --silent --preserve-dup-deps --mode=compile cc -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 -Iext/opcache/ -I/root/php-7.0.0/ext/opcache/ -DPHP_ATOM_INC -I/root/php-7.0.0/include -I/root/php-7.0.0/main -I/root/php-7.0.0 -I/root/php-7.0.0/ext/date/lib -I/usr/include/libxml2 -I/root/php-7.0.0/ext/sqlite3/libsqlite -I/root/php-7.0.0/TSRM -I/root/php-7.0.0/Zend    -I/usr/include -g -O2 -fvisibility=hidden    -c /root/php-7.0.0/ext/opcache/ZendAccelerator.c -o ext/opcache/ZendAccelerator.lo
...
Build complete.
Don't forget to run 'make test'.

编译结束时,应该能看到上面的提示,make test是非必要的,自行选择,另外要注意的一点,如果你是二次编译的话需要先清理之前的编译出来的文件,即在make之前执行一下make clean
到这里我们只要再执行一下make install就可以安装结束了。
这只是最基础的安装,我在这过程中省略了很多配置项,这样安装出来的软件是很“简陋”的,事实上在执行./configure操作时,有很多可选项供我们配置,但是不同软件可以添加的参数是不一样的,怎么看当前的软件可以添加哪些参数呢?在软件源码目录执行./configure --help就可以了:

root@ip-*** ~/php-7.0.0 # ./configure --help
...
Installation directories:
    --prefix=PREFIX         install architecture-independent files in PREFIX
                            [/usr/local]
...
SAPI modules:
    --enable-fpm            Enable building of the fpm SAPI executable
...

这里只列举了两个,一个是目标安装目录,一个是启用fpm,这些选项可以根据需要自行添加。
其实,很多软件通过添加软件源的方式还是可以用包管理器安装的,上面说的PHP7就是这样,不要打我???

其他资料

包管理器

Linux的包管理器有很多种,Cent OS有yum,Ubuntu有apt,还有别的像aptitude之类的,这里有一篇很好的文章供参考点我查看

oh-my-zsh

zsh也是一个非常强大的shell环境,可以用它来代替Linux系统原有的bash,这是一组zsh的配置,它可以让zsh更加好用,感兴趣的可以看我的另一篇文章oh-my-zsh小记

htop

这是一个运行在终端的用于查看系统资源使用情况和管理进程的图形化工具,使用yum或者apt-get等安装即可,完成后直接输入htop就可以使用了,比原生的top更强大。

查看原文

赞 5 收藏 42 评论 1

认证与成就

  • 获得 52 次点赞
  • 获得 12 枚徽章 获得 1 枚金徽章, 获得 4 枚银徽章, 获得 7 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2014-08-28
个人主页被 1k 人浏览