docker-compose 命令解析

傻男人 1年前 ⋅ 1085 阅读

docker-compose 命令解析

version 版本号

指定本 yml 依从的 compose 哪个版本制定的。

  • version 2只支持单机部署,version 3支持集群部署。

build 构建

指定为构建镜像上下文路径:

  • 例如 webapp 服务,指定为从上下文路径 ./dir/Dockerfile 所构建的镜像:
version: "3.7"
services:
  webapp:    # webapp 服务的服务名
    build: ./dir
  • 或者使用作为具有在上下文指定的路径的对象,以及可选的 Dockerfile 和 args:
version: "3.7"
services:
  webapp:
    build:
      context: ./dir
      dockerfile: myDockerfile
      shm_size:'2gb'    #或者shm_size:20480000
      cache_from:
        -alpine:latest
        -corp/web_app:3.14
      args:
        buildno: 1
      labels:
        - "com.example.description=Accounting webapp"
        - "com.example.department=Finance"
        - "com.example.label-with-empty-value"
      target: prod
  • 命令解析:
    • context:上下文路径。
    • cache_from:编写缓存解析镜像列表,此选项是v3.2中的新选项。
    • shm_size:设置容器 /dev/shm 分区的大小,值为表示字节的整数值或表示字符的字符串。
    • dockerfile:指定构建镜像的 Dockerfile 文件名。
    • args:添加构建参数,这是只能在构建过程中访问的环境变量。
    • labels:设置构建镜像的标签。
    • target:多层构建,可以指定构建哪一层。

depends_on 依赖关系

表达services之间的依赖关系,service依赖关系导致以下行为:

  • docker-compose up 以依赖性顺序启动服务。在以下示例中,先启动 db 和 redis ,才会启动 web。
  • docker-up up SERVICE自动包含SERVICE的依赖关系。 在下面的例子中,docker-compose up web也会创建并启动db和redis。
  • docker-compose stop :按依赖关系顺序停止服务。在以下示例中,web 在 db 和 redis 之前停止。
version: '3'
services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres

注意:web 服务不会等待 redis db 完全启动 之后才启动。 版本3不再支持condition形式depends_on。 使用版本3 Compose文件在swarm模式下部署堆栈depends_on时,将忽略该选项 。

deploy 指定与服务的部署和运行有关的配置

只在 swarm 模式下才会有用

version: "3.7"
services:
  redis:
    image: redis:alpine
    volumes:
      - "/localhost/postgres.sock:/var/run/postgres/postgres.sock"
      - "/localhost/data:/var/lib/postgresql/data"
    logging:
      driver:"json-file"
      options:
        max-size:"200k"
        max-file:"10"
    deploy:
      mode:replicated
      replicas: 6
      endpoint_mode: dnsrr
      labels: 
        description: "This redis service label"
      resources:
        limits:   # 资源限制
          cpus: '0.50'
          memory: 50M
        reservations:   # 资源保留
          cpus: '0.25'
          memory: 20M
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
      update_config:
        parallelism:2
        delay:10s
        order:stop-first
      placement:
        constraints:    # 限制条件
          - node.role == manager     # 限制db这个service部署至swarm的manager节点
          - engine.labels.operatingsystem == ubuntu 14.04
        preferences:    # 优先配置
          - spread: node.labels.zone

deploy下的子命令

endpoint_mode 访问集群服务的方式

endpoint_mode: vip

Docker 集群服务一个对外的虚拟 ip。所有的请求都会通过这个虚拟 ip 到达集群服务内部的机器。

endpoint_mode: dnsrr

DNS 轮询(DNSRR)。所有的请求会自动轮询获取到集群 ip 列表中的一个 ip 地址。

labels 设置标签

在服务上设置标签。可以用容器上的 labels(跟 deploy 同级的配置) 覆盖 deploy 下的 labels。其实就是对service的描述信息。这个标签仅在设置在service上,而不在service下的任何container设置。labels的值是key: value的形式。

mode 指定服务提供的模式

mode的值可以是global或replicated, 默认值是replicated。

  • replicated:复制服务,指定数量的容器,可以通过scale扩展container数量。
  • global:全局服务,每个群集节点只有一个容器,不能通过scale作横向扩展。

placement 指定服务限制条件与偏好

placement设置service的限制条件(constraints)和偏好(preferences)设置。

replicas 指定服务运行的服务数量

当模式为replicated(即mode: replicated)时,在初始化时,我们就可以通过relicas指定当前的service需要几个容器。

resources 配置服务器资源使用的限制

例如上例子,配置 redis 集群运行需要的 cpu 的百分比 和 内存的占用。避免占用资源过高出现异常。

  • limits就是限制当前service能够使用的硬件资源;
  • reservations就是为当前service保留的硬件资源;

restart_policy 重启策略

  • condition:重启的条件,取值none(无),on-failure(失败时),any(默认值是:any)。
  • delay:延迟时间。在尝试重启之间等待多长时间,默认值为0。
  • max_attempts:尝试重新启动容器的次数,超出次数,则不再尝试(默认值:一直重试)。
  • window:在决定重新启动是否成功之前等待多久。(默认值:0 表示立即决定)。

rollback_config 配置在更新失败的情况下应如何回滚服务

  • parallelism:一次要回滚的容器数。如果设置为0,则所有容器将同时回滚。
  • delay:每个容器组回滚之间等待的时间(默认为0s)。
  • failure_action:如果回滚失败,该怎么办。其中一个 continue 或者 pause(默认pause)。
  • monitor:每个容器更新后,持续观察是否失败了的时间 (ns|us|ms|s|m|h)(默认为0s)。
  • max_failure_ratio:在回滚期间可以容忍的故障率(默认为0)。
  • order:回滚期间的操作顺序。其中一个 stop-first(串行回滚),或者 start-first(并行回滚)(默认 stop-first )。

update_config:配置应如何更新服务,对于配置滚动更新很有用。

  • parallelism:一次更新的容器数。
  • delay:在更新一组容器之间等待的时间。
  • failure_action:如果更新失败,该怎么办。其中一个 continue,rollback 或者pause (默认:pause)。
  • monitor:每个容器更新后,持续观察是否失败了的时间 (ns|us|ms|s|m|h)(默认为0s)。
  • max_failure_ratio:在更新过程中可以容忍的故障率。
  • order:回滚期间的操作顺序。其中一个 stop-first(串行回滚),或者 start-first(并行回滚)(默认stop-first)。

注:仅支持 V3.4 及更高版本。

devices 指定设备映射列表

设置映射列表,与 Docker 客户端的 --device 参数类似

devices:
  - "/dev/ttyUSB0:/dev/ttyUSB0"

dns 自定义DNS服务器

自定义 DNS 服务器,与 --dns 具有一样的用途,可以是单个值或列表。

dns: 8.8.8.8

dns:
  - 8.8.8.8
  - 9.9.9.9

dns_search 自定义 DNS 搜索域

自定义 DNS 搜索域。可以是单个值或列表。

dns_search: example.com

dns_search:
  - dc1.example.com
  - dc2.example.com

entrypoint

在 Dockerfile 中有一个指令叫做 ENTRYPOINT 指令,用于指定接入点。在 docker-compose.yml 中可以定义接入点,覆盖 Dockerfile 中的定义 entrypoint: /code/entrypoint.sh 也可以是以下格式:

entrypoint:
    - php
    - -d
    - zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20100525/xdebug.so
    - -d
    - memory_limit=-1
    - vendor/bin/phpunit

env_file 环境变量

从文件添加环境变量。可以是单个值或列表的多个值 如果已经用 docker-compose -f FILE 指定了 Compose 文件,那么 env_file 路径值为相对于该文件所在的目录,但 environment 环境中的设置的变量会会覆盖这些值,无论这些值未定义还是为 None

env_file: .env 也可以是列表格式:

env_file:
  - ./common.env
  - ./apps/web.env
  - /opt/secrets.env
  • 环境配置文件 env_file 中的声明每行都是以 VAR=VAL 格式,其中以 # 开头的被解析为注释而被忽略,注意环境变量配置列表的顺序,例如下面例子
services:
  some-service:
    env_file:
      -a.env     #a.env中包含 VAR=1
      -b.env     #b.env中包含 VAR=hello

对于上示例中a.env与b.env文件中都有相同的变量,但是值不想相同,但是b.env配置在a.env,则a.env会被b.env值覆盖,此时 $VAR 值为 hello 这里所说的环境变量是对宿主机的 Compose 而言的,如果在配置文件中有 build 操作,这些变量并不会进入构建过程中,如果要在构建中使用变量还是首选 arg 标签。

environment

添加环境变量。您可以使用数组或字典、任何布尔值,布尔值需要用引号引起来,以确保 YML 解析器不会将其转换为 True 或 False。 添加环境变量,可以使用数组或字典。与上面的 env_file 选项完全不同,反而和 arg 有几分类似,这个标签的作用是设置镜像变量,它可以保存变量到镜像里面,也就是说启动的容器也会包含这些变量设置,这是与 arg 最大的不同 一般 arg 标签的变量仅用在构建过程中。而 environment 和 Dockerfile 中的 ENV 指令一样会把变量一直保存在镜像、容器中,类似 docker run -e 的效果。

environment:
  RACK_ENV: development
  SHOW: 'true'
或者
environment:
  -RACK_ENV=development
  -SHOW=true

expose 暴露端口

暴露端口,但不映射到宿主机,只被连接的服务访问。这个标签与 Dockerfile 中的 EXPOSE 指令一样,用于指定暴露的端口,但是只是作为一种参考,实际上 docker-compose.yml 的端口映射还得 ports 这样的标签

expose:
 - "3000"
 - "8000"

extra_hosts 添加主机名映射

就是往 /etc/hosts 文件中添加一些记录,与 Docker 客户端中的 --add-host 类似。

extra_hosts:
 - "somehost:162.242.195.82"
 - "otherhost:50.31.209.229"
  • 以上会在此服务的内部容器中 /etc/hosts 创建一个具有 ip 地址和主机名的映射关系:
162.242.195.82  somehost
50.31.209.229   otherhost

healthcheck 用于检测 docker 服务是否健康运行

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost"] # 设置检测程序
  interval: 1m30s # 设置检测间隔
  timeout: 10s # 设置检测超时时间
  retries: 3 # 设置重试次数
  start_period: 40s # 启动后,多少秒开始启动检测程序
  • interval,timeout 以及 start_period 都定为持续时间
  • test 必须是字符串或列表,如果它是一个列表,第一项必须是 NONE,CMD 或 CMD-SHELL ;如果它是一个字符串,则相当于指定CMD-SHELL 后跟该字符串。

如果需要禁用镜像的所有检查项目,可以使用 disable:true,相当于 test:["NONE"]

healthcheck:
  disable:true

image 镜像

从指定的镜像中启动容器,可以是存储仓库、标签以及镜像 ID,如果不存在,则会自动取拉取镜像

  • 以下格式都可以:
    • image: redis
    • image: ubuntu:14.04
    • image: tutum/influxdb
    • image: example-registry.com:4000/postgresql
    • image: a4bc65fd # 镜像id

logging 服务的日志记录配置

注意:只有驱动程序 json-file 和 journald 驱动程序可以直接从 docker-compose up 和 docker-compose logs 获取日志。使用任何其他方式不会显示任何日志。

driver 指定服务容器的日志记录驱动程序,默认值为json-file,与 --log-diver 选项一样

  • driver: "json-file"
  • driver: "syslog"
  • driver: "none"

json-file 具有限制存储日志量的选项,所以,使用键值对来获得最大存储大小以及最小存储数量。随着日志增长超出最大限制,旧日志文件将被删除以存储新日志

logging:
  driver: json-file
  options:
    max-size: "200k" # 单个文件大小为200k
    max-file: "10" # 最多10个文件

syslog 可以使用 syslog-address 指定日志接收地址。

logging:
  driver: syslog
  options:
    syslog-address: "tcp://192.168.0.42:123"

network_mode 设置网络模式

network_mode: "bridge"
network_mode: "host"
network_mode: "none"
network_mode: "service:[service name]"
network_mode: "container:[container name/id]"

networks 配置容器连接的网络,引用顶级 networks 下的条目

services:
  some-service:
    networks:
      some-network:
        aliases:
         - alias1
      other-network:
        aliases:
         - alias2
networks:
  some-network:
    # Use a custom driver
    driver: custom-driver-1
  other-network:
    # Use a custom driver which takes special options
    driver: custom-driver-2

aliases :同一网络上的其他容器可以使用服务名称或此别名来连接到对应容器的服务。

restart

注:swarm 集群模式,请改用 restart_policy。

restart: "no"  #是默认的重启策略,在任何情况下都不会重启容器。
restart: always   #容器总是重新启动。
restart: on-failure   #在容器非正常退出时(退出状态非0),才会重启容器。
restart: unless-stopped   #在容器退出时总是重启容器,但是不考虑在Docker守护进程启动时就已经停止了的容器

secrets 存储敏感数据

SHORT 语法

mysql:
  image: mysql
  environment:
    MYSQL_ROOT_PASSWORD_FILE: /run/secrets/my_secret
  secrets:
    - my_secret

secrets:
  my_secret:
    file: ./my_secret.txt

LONG 语法

LONG 语法可以添加其他选项

services:
  redis:
    image:redis:latest
    deploy:
      replicas:1
    secrets:
      -source:my_secret  #secret 名称
        target:redis_secret  #在服务任务容器中需要装载在 /run/secrets/ 中的文件名称,如果 source 未定义,那么默认为此值
        uid:'103'  #在服务的任务容器中拥有该文件的 UID 或 GID 。如果未指定,两者都默认为 0。
        gid:'103'  #在服务的任务容器中拥有该文件的 UID 或 GID 。如果未指定,两者都默认为 0。
        mode:0440  #以八进制表示法将文件装载到服务的任务容器中 /run/secrets/ 的权限。例如,0444 代表可读。
secrets:
  my_secret:
    file:./my_secret.txt
  my_other_secret:
    external:true

security_opt

修改容器默认的 schema 标签。为每个容器覆盖默认的标签。简单说来就是管理全部服务的标签,比如设置全部服务的 user 标签值为 USER

security-opt:
  - label:user:USER   # 设置容器的用户标签
  - label:role:ROLE   # 设置容器的角色标签
  - label:type:TYPE   # 设置容器的安全策略标签
  - label:level:LEVEL  # 设置容器的安全等级标签

stop_grace_period

指定在容器无法处理 SIGTERM (或者任何stop_signal 的信号),等待多久后发送 SIGKILL 信号关闭容器。默认的等待时间是 10 秒。

stop_grace_period: 1s # 等待 1 秒
stop_grace_period: 1m30s # 等待 1 分 30 秒 

stop_signal

设置另一个信号来停止容器。在默认情况下使用的 SIGTERM 来停止容器。设置另一个信号可以使用 stop_signal 标签: 以下示例,使用 SIGUSR1 替代信号 SIGTERM 来停止容器。 stop_signal: SIGUSR1

sysctls

在容器中设置的内核参数,可以为数组或字典

sysctls:
  net.core.somaxconn: 1024
  net.ipv4.tcp_syncookies: 0

sysctls:
  - net.core.somaxconn=1024
  - net.ipv4.tcp_syncookies=0

注意:使用(版本3)Compose文件在群集模式下部署堆栈时,将忽略此选项

tmpfs

挂载临时文件目录到容器内部。与 run 的参数一样效果,可以是单个值或列表的多个值。

tmpfs: /run

tmpfs:
  - /run
  - /tmp
  • 在容器内安装临时文件系统。Size参数指定tmpfs mount的大小(以字节为单位)。默认无限制。
- type: tmpfs
     target: /app
     tmpfs:
       size: 1000

注意:使用(版本3-3.5)Compose文件在群集模式下部署堆栈时,将忽略此选项 。

ulimits

覆盖容器默认限制。可以单一地将限制值设为一个整数,也可以将soft/hard 限制指定为映射

ulimits:
  nproc: 65535
  nofile:
    soft: 20000
    hard: 40000

volumes

将主机的数据卷或着文件挂载到容器里。挂载一个目录或者一个已存在的数据卷容器,可以直接使用 HOST:CONTAINER 这样的格式,或者使用 HOST:CONTAINER:ro 这样的格式,后者对于容器来说,数据卷是只读的,这样可以有效保护宿主机的文件系统

version: "3.7"
services:
web:
  image: nginx:alpine
  volumes:
    - type: volume
      source: mydata
      target: /data
      volume:
        nocopy: true
    - type: bind
      source: ./static
      target: /opt/app/static
db:
  image: postgres:latest
  volumes:
    - "/var/run/postgres/postgres.sock:/var/run/postgres/postgres.sock"
    - "dbdata:/var/lib/postgresql/data"
volumes:
  mydata:
  dbdata:

此示例显示服务使用的命名卷(mydata)web以及为单个服务(db服务 下的第一个路径volumes)定义的绑定安装。 该db服务还使用名为dbdata(db服务中的第二个路径volumes)的命名卷,但使用旧字符串格式定义它以安装命名卷。必须在顶级volumes键下列出命名卷

SHORT语法

可以选择在主机(HOST:CONTAINER)或访问模式(HOST:CONTAINER:ro)上指定路径。 可以在主机上挂载相对路径,该路径相对于正在使用的 Compose 配置文件的目录进行扩展。相对路径应始终以 . 或 .. 开头

volumes:
# Just specify a path and let the Engine create a volume
- /var/lib/mysql
# Specify an absolute path mapping
- /opt/data:/var/lib/mysql
# Path on the host, relative to the Compose file
- ./cache:/tmp/cache
# User-relative path
- ~/configs:/etc/configs/:ro
# Named volume
- datavolume:/var/lib/mysql

LONG语法

  • LONG 语法有些附加字段
    • type:安装类型,可以为 volume、bind 或 tmpfs
    • source:安装源,主机上用于绑定安装的路径或定义在顶级 volumes密钥中卷的名称 ,不适用于 tmpfs 类型安装。
    • target:卷安装在容器中的路径
    • read_only:标志将卷设置为只读
    • bind:配置额外的绑定选项
    • propagation:用于绑定的传播模式
    • volume:配置其他卷选项
    • nocopy:创建卷时禁止从容器复制数据的标志
    • tmpfs:配置额外的 tmpfs 选项
    • size:tmpfs 的大小,以字节为单位
    • consistent:完全一致。容器运行时和主机始终保持相同的安装视图。这是默认值。
    • cached:主机的mount视图是权威的。在主机上进行的更新在容器中可见之前可能会有延迟。
    • delegated:容器运行时的mount视图是权威的。在容器中进行的更新在主机上可见之前可能会有延迟。
version: "3.7"
services:
  web:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - type: volume
        source: mydata
        target: /data
        volume:
          nocopy: true
      - type: bind
        source: ./static
        target: /opt/app/static
networks:
  webnet:
volumes:
  mydata:

init

在容器内运行init,转发信号并重新获得进程。将此选项设置true是为服务启用此功能。

version:"3.7"
services:
  web:
    image:alpine:latest
    init:true

isolation

指定容器的隔离技术。在Linux上,唯一支持的值是default。在Windows中,可接受的值是default,process和 hyperv。

links

链接到其它服务的中的容器,可以指定服务名称也可以指定链接别名(SERVICE:ALIAS),与 Docker 客户端的 --link 有一样效果,会连接到其它服务中的容器。

web:
  links:
   -db
   -db:database
   -redis

networks

加入指定网络

services:
  some-service:
    networks:
     -some-network
     -other-network

aliases

同一网络上的其他容器可以使用服务器名称或别名来连接到其他服务的容器,相同的服务可以在不同的网络有不同的别名。

services:
  some-service:
    networks:
      some-network:
        aliases:
         -alias1
         -alias3
      other-network:
        aliases:
         -alias2
  • 下面实例中,提供 web 、worker以及db 服务,伴随着两个网络 new 和 legacy 。
version:"3.7"
services:
  web:
    image:"nginx:alpine"
    networks:
      -new
  worker:
    image:"my-worker-image:latest"
    networks:
      -legacy
  db:
    image:mysql
    networks:
      new:
        aliases:
          -database
      legacy:
        aliases:
          -mysql
networks:
  new:
  legacy:

ipv4_address、ipv6_address

为服务的容器指定一个静态 IP 地址

version:"3.7"
services:
  app:
    image:nginx:alpine
    networks:
      app_net:
        ipv4_address:172.16.238.10
        ipv6_address:2001:3984:3989::10
networks:
  app_net:
    ipam:
      driver:default
      config:
        -subnet:"172.16.238.0/24"
        -subnet:"2001:3984:3989::/64"

pid

将 PID 模式设置为主机 PID 模式,可以打开容器与主机操作系统之间的共享 PID 地址空间。使用此标志启动的容器可以访问和操作宿主机的其他容器,反之亦然

pid: "host"

ports 映射端口

SHORT 语法

可以使用 HOST:CONTAINER 的方式指定端口,也可以指定容器端口(选择临时主机端口),宿主机会随机映射端口

ports:
 -"3000"
 -"3000-3005"
 -"8000:8000"
 -"9090-9091:8080-8081"
 -"49100:22"
 -"127.0.0.1:8001:8001"
 -"127.0.0.1:5000-5010:5000-5010"
 -"6060:6060/udp"

注意:当使用 HOST:CONTAINER 格式来映射端口时,如果使用的容器端口小于60可能会得到错误得结果,因为YAML 将会解析 xx:yy 这种数字格式为 60 进制,所以建议采用字符串格式。

LONG 语法

LONG 语法支持 SHORT 语法不支持的附加字段

  • target:容器内的端口
  • published:公开的端口
  • protocol: 端口协议(tcp 或 udp)
  • mode:通过host 用在每个节点还是哪个发布的主机端口或使用 ingress 用于集群模式端口进行平衡负载
ports:
  -target:80
    published:8080
    protocol:tcp
    mode:host

userns_mode

如果Docker守护程序配置了用户名称空间,则禁用此服务的用户名称空间。

userns_mode: "host"

注意: 使用(版本3)Compose文件在群集模式下部署堆栈时,将忽略此选项 。

driver

指定应为此卷使用哪个卷驱动程序。默认为Docker Engine配置使用的任何驱动程序,在大多数情况下是local。如果驱动程序不可用,则在docker-compose up尝试创建卷时Engine会返回错误 。

driver: foobar

driver_opts

将选项列表指定为键值对,以传递给此卷的驱动程序。

volumes:
  example:
    driver_opts:
      type: "nfs"
      o: "addr=10.40.0.199,nolock,soft,rw"
      device: ":/docker/example"

external

如果设置为true,则指定已在Compose之外创建此卷。docker-compose up不会尝试创建它,如果它不存在则引发错误。

  • 在下面的示例中,[projectname]_dataCompose 不是尝试创建一个被调用的卷,而是 查找简单调用的现有卷data并将其挂载到db服务的容器中。
version: "3.7"
services:
  db:
    image: postgres
    volumes:
      - data:/var/lib/postgresql/data
volumes:
  data:
    external: true
  • 还可以在Compose文件中与用于引用它的名称分别指定卷的名称:
volumes:
  data:
    external:
      name: actual-name-of-volume

name

为此卷设置自定义名称。name字段可用于引用包含特殊字符的卷。该名称按原样使用,不会使用堆栈名称作为范围。

version: "3.7"
volumes:
  data:
    name: my-app-data
  • 它也可以与external参数一起使用:
version: "3.7"
volumes:
  data:
    external: true
    name: my-app-data

external_links

链接到 docker-compose.yml 外部的容器,甚至并非 Compose 项目文件管理的容器。参数格式跟 links 类似。 在使用Docker过程中,会有许多单独使用 docker run 启动的容器的情况,为了使 Compose 能够连接这些不在docker-compose.yml 配置文件中定义的容器,那么就需要一个特殊的标签,就是 external_links,它可以让Compose 项目里面的容器连接到那些项目配置外部的容器(前提是外部容器中必须至少有一个容器是连接到与项目内的服务的同一个网络里面)。

external_links:
 -redis_1
 -project_db_1:mysql
 -project_db_1:postgresql

cap_add,cap_drop

添加或删除容器拥有的宿主机的内核功能。

cap_add:
  - ALL # 开启全部权限
cap_drop:
  - SYS_PTRACE # 关闭 ptrace权限

cgroup_parent

为容器指定父 cgroup 组,意味着将继承该组的资源限制。

cgroup_parent: m-executor-abcd

command

覆盖容器启动的默认命令。

command: ["bundle", "exec", "thin", "-p", "3000"]

container_name

指定自定义容器名称,而不是生成的默认名称。

container_name: my-web-container

全部评论: 0

    我有话说: