Docker系统性入门(三)

网络

  • 在一台宿主机上创建多个container,之间可以ping通,为啥呢?通过docker network ls查看各网络
    1
    2
    • docker0作为网关,互联着宿主机的多个容器,所以能互相ping
  • 有个查看网桥的命令:brctl show,这有两个接口,(不太理解网桥技术,interface?)
    3
    4
    • 访问外网需要NAT,都要经过宿主机eth0,外部无法区分是哪个docker container在请求
    • 可以通过ip routeiptables --list -t nat等命令查看转发规则
    • 我觉得可以学学curl,命令行版的postman
  • 自己创建bridge:docker network create
    5
  • 当然,可以让container使用自己的bridge转发:docker run -idt --name self-bri --network self-bridge busybox /bin/sh
    6
    • 怎么让这容器连上另一个网桥(bridge)呢:docker network connect bridge self-bri
    • 此时进入容器查看,就多了个网卡,用来接入到docker0的网络,属于此网络的容器也都能ping通
    • 因为用的是B类网络,16位子网掩码,17/18是两个网段,connect就是添加路由
      7
    • 类似的,可以使用disconnect断开连接,就ping不通那边的容器了~
      8
  • 这里连接新网络时新增了虚拟网卡eth1,因为不是一个子网吧!不能增加路由吗?
  • 可以指定网桥的网段(子网):docker network create -d bridge --gateway 172.200.0.1 --subnet 172.200.0.0/16 self-subnet
    • 怎么让容器使用指定的子网呢?connect或者在创建时使用--network
  • 类似的,可以使用docker network prune清除不用的网络

端口映射

  • 之前一直在使用-p参数创建容器,将容器内服务的端口映射到宿主机,方便外部访问
  • 环境:两个网络之间(例如两台虚拟机):roy和roy01
    • 在roy上使用NGINX镜像建立服务,docker run -idt --name nginx-doc nginx sh,可以访问roy01:ping 192.168.109.129
    • 但roy01不能访问nginx服务,所以需要:docker run -idt --name nginx-doc -p 8080:80 nginx sh
    • 此时本地可以127.0.0.1:8080访问,也可以在roy01通过:192.168.109.3:8080访问
  • 使用sudo iptables -t nat -nvxL可以看到转发规则
  • 之前还在Dockerfile中使用过EXPOSE 5000,他的主要作用是提示,方便我们inspect到这个镜像使用的端口,再用-p映射出来

host&null

  • docker默认有三种网络类型,上面说了bridge网桥,会给容器自动设置docker0网段的IP
    1
  • host的特点是完全复制主机的网络:docker run -idt --name host-demo --network host busybox sh
  • null的特点是不给容器设置任何网络,完全用户自定义,用的较少

namespace

  • Linux的命名空间是一种隔离技术,常用的Namespace有 user namespace, process namespace, network namespace等
  • docker的网络正是使用了network namespace,不同的容器有各自的IP和路由表
  • 这里使用brctl命令手动实现一个隔离的系统
    1
    • ns1和ns2使用namespace技术互相隔离,通过建立bridge通信
  • 实验脚本:创建namespace并定义interface的IP,然后UP
    #!/bin/bash
    
    bridge=$1
    namespace=$2
    addr=$3
    
    vethA=veth-$namespace
    vethB=eth00
    
    sudo ip netns add $namespace
    sudo ip link add $vethA type veth peer name $vethB
    
    sudo ip link set $vethB netns $namespace
    sudo ip netns exec $namespace ip addr add $addr dev $vethB
    sudo ip netns exec $namespace ip link set $vethB up
    
    sudo ip link set $vethA up
    
    sudo brctl addif $bridge $vethA
    
    # 建立网桥
    sudo brctl addbr mydocker0
    # 搭起来
    sh add-ns-to-br.sh mydocker0 ns1 172.16.1.1/16
    sh add-ns-to-br.sh mydocker0 ns2 172.16.1.2/16
    sudo ip link set dev mydocker0 up
    
  • 测试
    # 进到ns1的环境
    sudo ip netns exec ns1 bash
    ip a
    ping 172.16.1.2
    
  • 小练习

compose

  • docker-compose,将我们准备容器、设置网络的步骤定义在.yaml文件中,方便我们容器化
  • 安装
    # 源码链接:https://github.com/docker/compose/releases,到了2.3.3
    sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    sudo chmod +x /usr/local/bin/docker-compose
    docker-compose --version
    
    # 也可以通过python安装,推荐这种,可能要升级一下pip
    # https://www.cnblogs.com/yangjisen/p/13171063.html
    pip install docker-compose
    sudo ln -s /usr/local/python37/bin/docker-compose /usr/bin/docker-compose
    

语法

  • yaml文件的基本结构(语法),就相当于shell脚本,把平时的Linux命令组合起来自动化
    version: "3.8"
    
    services: # 容器
      servicename: # 服务名,这个名字也是内部 bridge 网络可解析使用的 name(DNS)
        container_name: # 容器名字,默认是servicename
        image: # 镜像的名字
        command: # 可选,如果设置,则会覆盖默认镜像里的 CMD命令
        environment: # 可选,相当于 docker run里的 --env
        volumes: # 可选,相当于docker run里的 -v
        networks: # 可选,相当于 docker run里的 --network
        ports: # 可选,相当于 docker run里的 -p
      servicename2:
    
    volumes: # 可选,相当于 docker volume create
    
    networks: # 可选,相当于 docker network create
    

demo

  • 将下面的步骤改造为使用yaml
    # 准备app.py文件
    from flask import Flask
    from redis import Redis
    import os
    import socket
    
    app = Flask(__name__)
    # 连接redis服务器
    redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379)
    
    
    @app.route('/')
    def hello():
        redis.incr('hits')
        return f"Hello Container World! I have been seen {redis.get('hits').decode('utf-8')} times and my hostname is {socket.gethostname()}.\n"
    # hostname=container ID
    
    # Dockerfile,准备镜像
    FROM python:3.9.5-slim
    
    RUN pip install flask redis && \
        groupadd -r flask && useradd -r -g flask flask && \
        mkdir /src && \
        chown -R flask:flask /src
    
    USER flask
    
    COPY app.py /src/app.py
    
    WORKDIR /src
    
    ENV FLASK_APP=app.py REDIS_HOST=redis
    
    EXPOSE 5000	# 提示使用5000端口
    
    CMD ["flask", "run", "-h", "0.0.0.0"]
    
    docker image build -t flask-demo .
    # 建个网络
    docker network create -d bridge demo-network
    docker image pull redis
    # 搞个redis容器
    docker container run -d --name redis-server --network demo-network redis
    # flask容器,这里通过--env动态地将redis server的host设置为环境变量,container也可获取
    docker container run -d --network demo-network --name flask-demo --env REDIS_HOST=redis-server -p 5000:5000 flask-demo	# 这里用到了docker0可以解析所连容器域名(host)的功能
    # 访问
    http://127.0.0.1:5000
    
    • 这里实现的功能是:每次访问网页,都会使用Redis服务器的incr功能计数并显示
  • 改写为使用docker-compose完成,当然,镜像需要自己准备好
    # docker-compose.yml
    version: "3.8"
    services:
      flask-demo:
        image: flask-demo:latest
        environment:
            -  REDIS_HOST=redis-server	# 传给容器的动态ENV
        networks:
            - demo-network
        ports:
            - 8080:5000	# 映射到8080,flask默认5000
      redis-server:
        image: redis:latest
        networks:
            - demo-network
          
    networks:
      # 先不自定义网段,都默认,所以应该是172.18.0.1
      demo-network:
    
  • 怎么用这个yml文件呢?类似Dockerfile文件的命名和使用,配置需要放在当前执行命令的文件夹下(也可 -f),且名称为docker-compose.yml
    • 这个文件夹的名字最好设置为项目名,因为后续创建的网络和容器都会用文件夹名为前缀
    • 命令:docker-compose up -d,使用-d后台运行
      1
    • 有的容器没起来不用怕,问题解决了再起;这个前缀可以在up时可使用-p参数修改,但后面的命令就都要带上-p,所以不建议使用
      2
    • 还带了个后缀,这个和scale有关!反正它用名称管理,最好默认
    • OK,目前它已经帮我们建立了容器和网络,浏览器能正常访问并计数!
    • docker-compose down(Stop and remove resources),docker-compose ps,都要在yml文件夹下

镜像

  • 上面的镜像都是拉过来的或者build好的,compose不能biu?当然可以
    version: "3.8"
    services:
      flask-demo:
        build: ./flask	# 这里放build镜像所需要的Dockerfile和代码,或者:
          context: ./flask
          dockerfile: Dockerfile-dev	# 通过传参指定不同的Dockerfile
        image: flask-demo:latest	# 默认build出来的镜像还是以文件夹名为前缀,设置image就可以重命名为flask-demo
        enviroment:
        ...
    
  • 命令行执行:docker-compose build,这样就可以只build镜像,并不会创建容器
    • 还可以直接docker-compose up -d,连build带创建容器
    • 也可以指定service:docker-compose build flask-demo
  • 可以直接拉取镜像:docker-compose pull,当然,需要yml文件中定义service
    • 也可以指定service:docker-compose pull service-name
    • 这些命令各司其职,但比较智能
  • docker-compose可build/pull/run,但要通过service-name;在维护的过程中,最常用的是三个命令
    • docker-compose up -d --build
    • docker-compose restart
    • docker-compose up -d --remove-orphans,yml文件修改,删掉某些service之后会有孤儿容器

网络

  • 详细介绍一下yml中network的定义
    • 之前说过docker网络会给容器建立一个内部的DNS服务器,所以容器之间可以互相ping名称
    • docker-compose不仅可以ping容器名字,还可以 ping 服务名;如果我们不定义network还会创建默认的bridge网络
  • 测试
    # Dockerfile
    FROM alpine:latest
    
    RUN apk update && apk upgrade && \
        apk add --no-cache net-snmp-tools && \
        mkdir /var/lib/net-snmp && \
        mkdir /var/lib/net-snmp/mib_indexes && \
        apk add --no-cache snmptt && \
        apk add --no-cache openssh-client && \
        apk add --no-cache busybox-extras && \
        apk add --no-cache curl && \
        apk add --no-cache iperf3 && \
        apk add --no-cache bind-tools && \
        apk add --no-cache nmap && \
        apk add --no-cache scanssh && \
        apk add --no-cache mtr
    
    WORKDIR /omd
    
    VOLUME ["/omd"]
    
    CMD []
    
    # docker-compose.yml
    version: "3.8"
    
    services:
      box1:
        build: ./
        image: roy/net:latest
        command: /bin/sh -c "while true;do sleep 3600;done"
    
      box2:
        build: ./
        image: roy/net:latest
        command: /bin/sh -c "while true;do sleep 3600;done"
    
    # 如果build出问题,直接拉吧
    version: "3.8" 
    
    services:
      box1:
        image: xiaopeng163/net-box:latest
        command: /bin/sh -c "while true;do sleep 3600;done"
    
      box2:
        image: xiaopeng163/net-box:latest
        command: /bin/sh -c "while true;do sleep 3600;done"
    
    3
    • 网络、容器都是以文件夹为前缀
  • 配置网络属性
    version: "3.8"
    
    services:
      box1:
        image: xiaopeng163/net-box:latest
        command: /bin/sh -c "while true;do sleep 3600;done"
        networks:
            - test-network1
            - test-network2
    
      box2:
        image: xiaopeng163/net-box:latest
        command: /bin/sh -c "while true;do sleep 3600;done"
        networks:
            - test-network1
    
    networks:
      test-network1:
        ipam:
          driver: default
          config:
            - subnet: "172.18.126.0/24"
      test-network2:
        ipam:
          driver: default
    

水平扩展

  • 切换到之前的flask文件夹下,修改yml文件
    version: "3.8"
    
    services:
      flask-demo:
        build: ./
        image: flask-demo:latest
        environment:
            - REDIS_HOST=redis-server
        networks:
            - demo-network
      
      redis-server:
        image: redis:latest
        networks:
            - demo-network
    
      # 这里需要用到client,到容器内网请求,因为没有暴露端口 ports 到宿主机
      # 可以在client里,把某个进程暴露出来,这个进程负责请求flask
      client:
        image: xiaopeng163/net-box:latest
        command: sh -c "while true; do sleep 3500; done;"
        # 要么别指定,要么都指定
        networks:
            - demo-network
          
    networks:
      demo-network:
    
    1
  • 扩展出多个flask-demo service,这就是 scale ,注意看后缀数字
  • 此时还有个问题,我们进client后ping flask-demo啥结果?
    • 每次返回的IP不确定,可能是flask-demo1/2/3,做了负载均衡
  • 上面用client在子网内请求,因为flask要扩展,无法定义ports暴露给宿主机,这里用NGINX代理
    # nginx.conf
    server {	# 这个server可以理解为NGINX代理服务器
      listen 80 default_server;	# default表示127.0.0.1,代理本机,暴露谁就监听谁=代理谁(拦截)
      location / {
        proxy_pass http://flask:5000;	# 转发请求给这个地址; 使用flask域名解析;在一个网段即可,不需要关心服务器和代理服务器是否为一台机器
      }
    }
    # 这个配置表示所有80的请求都代理到访问 http://flask:5000
    # NGINX只是推荐参考:https://blog.youkuaiyun.com/Roy_Allen/article/details/114672519?spm=1001.2014.3001.5502
    
    # docker-compose.yml
    version: "3.8"
    
    services:
      flask:
        build:
          context: ./flask
          dockerfile: Dockerfile
        image: flask-demo:latest
        environment:
          - REDIS_HOST=redis-server
        networks:
          - backend
          - frontend
    
      redis-server:
        image: redis:latest
        networks:
          - backend
    
      nginx:
        image: nginx:stable-alpine
        ports:
          - 8000:80
        depends_on:
          - flask
        volumes:
          - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
          - ./var/log/nginx:/var/log/nginx
        networks:
          - frontend
    
    networks:
      backend:
      frontend:
    
    • 这里有两个网络,因为flask和redis互相配合(后端),NGINX代理只和flask有关
    • 这里还将NGINX的配置和日志做了mount,到本地保存
    • 执行:docker-compose up -d
      5
    • 访问:http://127.0.0.1:8000/

ENV

  • 可以在yml文件中设置环境变量,就是environment,这个例子里主要是给app.py用
    version: "3.8"
    
    services:
      flask:
        build:
          context: ./flask
          dockerfile: Dockerfile
        image: flask-demo:latest
        environment:
          - REDIS_HOST=redis-server
          - REDIS_PASS=${REDIS_PASSWORD}
        networks:
          - backend
          - frontend
    
      redis-server:
        image: redis:latest
        # redis-server redis-cli是redis定义的命令
        command: redis-server --requirepass ${REDIS_PASSWORD}
        networks:
          - backend
    
      nginx:
        image: nginx:stable-alpine
        ports:
          - 8000:80
        depends_on:
          - flask
        volumes:
          - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
          - ./var/log/nginx:/var/log/nginx
        networks:
          - frontend
    
    networks:
      backend:
      frontend:
    
    # app.py
    from flask import Flask
    from redis import StrictRedis
    import os
    import socket
    
    app = Flask(__name__)
    redis = StrictRedis(host=os.environ.get('REDIS_HOST', '127.0.0.1'),
                        port=6379, password=os.environ.get('REDIS_PASS'))
    
    
    @app.route('/')
    def hello():
        redis.incr('hits')
        return f"Hello Container World! I have been seen {redis.get('hits').decode('utf-8')} times and my hostname is {socket.gethostname()}.\n"
    
    • 执行:docker-compose up -d --build,不太智能了,你改了py没给你build直接用的缓存image
  • 新建配置文件,这里用默认名字,也可以在up的时候用--env-file指定
    # .env
    REDIS_PASSWORD=roy123
    
    • 这种将环境变量单独配置的方法可以提高安全性,将env文件加到-gitignore
    • docker-compose config可以看到网络和服务(容器)的配置

服务依赖

  • 服务依赖和健康检查经常一起使用,先看什么是健康检查
    # Dockerfile
    FROM python:3.9.5-slim
    
    RUN pip install flask redis && \
        apt-get update && \
        apt-get install -y curl && \
        groupadd -r flask && useradd -r -g flask flask && \
        mkdir /src && \
        chown -R flask:flask /src
    
    USER flask
    
    COPY app.py /src/app.py
    
    WORKDIR /src
    
    ENV FLASK_APP=app.py REDIS_HOST=redis
    
    EXPOSE 5000
    # HEALTHCHECK 的更多用法可以看官网
    # 这里表示30s请求一次,如果失败三次则unhealthy
    HEALTHCHECK --interval=30s --timeout=3s \
        CMD curl -f http://localhost:5000/ || exit 1
    
    CMD ["flask", "run", "-h", "0.0.0.0"]
    # app.py还是需要密码的那个
    
    • 执行:docker image build -t flask-healthy .docker container run -d --network mybridge --name flask-h --env REDIS_PASS=abc123 flask-healthy ,这里要通过run指定ENV,REDIS_HOST在镜像指定了
    • 一分半后docker ps看应该会显示unhealthy,docker container inspect ID
      6
    • 启动redis,指定到相同网段:docker run -d --name redis --network mybridge redis:latest redis-server --requirepass abc123;(还是没看到healthy)
      7
  • 在docker-compose中怎么检查呢?其实就是配置文件的不同,在官网都能找到例子
    version: "3.8"
    
    services:
      flask:
        build:
          context: ./flask
          dockerfile: Dockerfile
        image: flask-demo:latest
        environment:
          - REDIS_HOST=redis-server
          - REDIS_PASS=${REDIS_PASSWORD}
        healthcheck:
          test: ["CMD", "curl", "-f", "http://localhost:5000"]
          interval: 30s
          timeout: 3s
          retries: 3
          start_period: 40s
        depends_on:
          redis-server:
            condition: service_healthy
        networks:
          - backend
          - frontend
    
      redis-server:
        image: redis:latest
        command: redis-server --requirepass ${REDIS_PASSWORD}
        healthcheck:
          test: ["CMD", "redis-cli", "ping"]
          interval: 1s
          timeout: 3s
          retries: 30
        networks:
          - backend
    
      nginx:
        image: nginx:stable-alpine
        ports:
          - 8000:80
        depends_on:
          flask:
            condition: service_healthy
        volumes:
          - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
          - ./var/log/nginx:/var/log/nginx
        networks:
          - frontend
    
    networks:
      backend:
      frontend:
    
    • healthcheck各属性定义了之前Dockerfile中HEALTHCHECK的内容,这里可以更改redis密码让flask失败(unhealthy)
    • depends_on即服务(容器)依赖,所依赖的服务必须先启动
  • 注意:因为服务依赖,所以如果flask不健康NGINX会启动失败,一直等到healthy状态,docker-compose up -d才能成功!

练习

  • 投票APP,我们选择基于Linux的docker-compose.yml玩一下,练习yml文件的写法
  • 官方文档:还有很多compose的命令可以学习

小结

  • docker-compose解放了我们管理容器的双手,从build到run,还有配置网络、水平扩展、服务依赖等都能定义
  • 但不建议在生产环境中使用
    • 多机器如何管理?
    • 如果跨机器做scale横向扩展?
    • 容器失败退出时如何新建容器确保服务正常运行?
    • 如何确保零宕机时间?
    • 如何管理密码,Key等敏感数据?
  • docker-swarm分布式管理工具来了
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瑞士_R

修行不易...

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值