Docker 详细

Prerequisite

很久之前写过 Docker 的博客,但那时也只是随意了解一下,现在打算重新学习(但我依然没有实际场景需要用到,因此以后可能还会再写一次)

参考文章:廖雪峰【一文读懂Docker原理】
参考文章:Install Docker Engine on Ubuntu
参考文章:浅析 Dockerfile 中 RUN、CMD 以及 ENTRYPOINT 指令的异同
参考文章:Docker 常用命令
参考视频:Docker技术从0到1全覆盖(对应笔记
参考报错:failed to solve with frontend dockerfile.v0: failed to create LLB definition
使用镜像源:清华大学开源软件镜像站

Docker 原理

Docker 的容器是一种对进程进行隔离的运行环境(进程是 Linux 操作系统执行任务的最小单元)
Docker 本质就是帮助我们设置好隔离环境后,启动容器中不同隔离的进程(Linux 系统本身负责隔离)

隔离的方法有三种:

  • 第一种就是进程之间看不到彼此,这是由 Linux 的 Cgroup 机制实现的。进程隔离的结果就是以隔离方式启动的进程看到的自身进程 ID 总是 1,且看不到系统的其他进程。
  • 第二种就是隔离系统真实的文件系统。Docker 利用 Linux 的 mount 机制,给每个隔离进程挂载了一个虚拟的文件系统,使得一个隔离进程只能访问这个虚拟的文件系统,无法看到系统真实的文件系统。至于这个虚拟的文件系统应该长什么样,这就是制作 Docker 镜像要考虑的问题。比如我们的 Python 程序要正常运行,需要一个 Python3 解释器,需要把用到的第三方库如 psutil 引入进来,这些复杂的工作被简化为一个 Dockerfile,再由 Docker 把这些运行时的依赖打包,就形成了 Docker 镜像。我们可以把一个 Docker 镜像看作一个 zip 包,每启动一个进程,Docker 都会自动解压 zip 包,把它变成一个虚拟的文件系统。
  • 第三种就是网络协议栈的隔离(以下面要运行 ZooKeeper 和 Kafka 举例)
# 将 zookeeper 进程的端口号映射到宿主机,从而在宿主机上访问 zookeeper
# 但这只是 zookeeper 进程的端口映射,如果需要映射多个进程,需要利用 Docker Compose
docker run -p 2181:2181 zookeeper:latest

因为 ZooKeeper 和 Kafka 都有监听的端口,但 Linux 可以为进程隔离网络,Docker 默认启动的 ZooKeeper 和 Kafka 进程拥有自己的网络名字空间,与宿主机不同。利用 Docker Compose,把 ZooKeeper 和 Kafka 运行在同一个网络名字空间里,并通过 zookeeper:2181 来访问 ZooKeeper 端口,让 Docker 自动把 zookeeper 名字解析为动态分配的 IP 地址

Docker Linux 下载

$ sudo apt-get install apt-transport-https ca-certificates curl gnupg2 software-properties-common

$ sudo mkdir -p /etc/apt/keyrings

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

$ echo \
   "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu \
   $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

$ sudo apt-get update

$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

Docker 用法

基础命令
# 登入 Docker Hub(用户名为:courserli)
docker login -u courserli

# 从 Docker Hub 下载镜像(名字为:courserli 的 hello-docker)
docker pull courserli/hello-docker

# 分享镜像到 Docker Hub(名字为:courserli 的 hello-docker,默认 tag 为:latest)
docker push courserli/hello-docker

# 清除缓存(会停止全部容器的运行)
docker system prune
镜像命令
# 查看全部 docker 镜像
docker image ls

# 删除 docker 镜像
docker image rm 镜像名称

# 构建本地镜像(由当前目录下的 Dockerfile 文件构建,构建镜像名为 tuanzi/test)
docker build . -t tuanzi/test
进程命令
# 启动 docker 容器(-it 是交互式操作终端,sagemath/sagemath 是镜像名,--rm 表示在容器退出时自动清理容器内部的文件系统)
docker run -it --rm sagemath/sagemath

# 启动 docker 容器(-d 后台运行,-p 端口映射,前者为容器内部端口,后者为映射本地端口,如果镜像名在本地找不到,会自动联网 pull)
docker run -d -p 80:80 docker/getting-started

# 启动 docker 容器(-v 是挂载路径,后接挂载本地路径和容器路径)
docker run -it --rm -v 路径:/share/data tuanzi/test

# 在运行的 docker 容器中执行命令(ls 就是执行的命令)
docker exec -it nginx ls

# 进入正在运行的 docker 容器(/bin/bash 命令相当于弹出 shell 来,可以看作是进入了)
docker exec -it nginx /bin/bash

# 查看正在运行的 docker 容器进程
docker ps

# 查看已经停止的 docker 容器进程
docker ps -a

# 停止 docker 容器进程(参数是 CONTAINER ID)
docker rm -f 042c94072180
Docker file

这里以一个实际的例子回顾,这是我的本地的目录

  • test
    • db
      • data
    • Dockerfile
    • run.sh

执行命令的路径与 Dockerfile 同级

  • Dockerfile 文件:
FROM ubuntu:16.04

COPY run.sh /run.sh

VOLUME /share/data

RUN useradd nss

RUN sed -i s@/deb.debian.org/@/mirrors.aliyun.com/@g /etc/apt/sources.list && \
    sed -i s@/security.debian.org/@/mirrors.aliyun.com/@g /etc/apt/sources.list
    
RUN ["apt-get", "update", "-y"]

WORKDIR /home/nss

RUN chmod +x /run.sh

ENV FLAG=NSSCTF{test_flag}

EXPOSE 80/tcp

CMD [ "helloworld" ]

USER nss

ENTRYPOINT [ "/run.sh" ]
  • run.sh 文件:
#! /bin/bash
echo "Run with user `whoami`"
echo "Run with directory `pwd`"
echo "Run with environment $FLAG"
echo "Run with parameter $1"
tail -f /dev/null

read v
while [ "$v" == "n" ];
do
    cat /share/data/data
    read v
done;

关于内容的具体细节可以看这篇文章,我来特别说一下:

  • 如何挂载?

一般构建的命令是 docker build . -t tuanzi/test,然后运行是 docker run -it --rm tuanzi/test;正如 Dockerfile 文件中的 VOLUME /share/data 所描述,我需要在命令行中添加挂载路径,即 docker run -it --rm -v 路径:/share/data tuanzi/test,此时容器内 data 文件的内容会随着本地 data 文件而改变

  • 如何直接进入容器中(而不是终端交互)?

去掉 Dockerfile 文件中的 RUN、CMD、ENTRYPOINT 的命令,或者执行 docker run -it --rm tuanzi/test /bin/sh 命令,用于覆盖掉 Dockerfile 文件中的 ENTRYPOINT

针对 RUN、CMD 和 ENTRYPOINT,大佬给出了总结:

  • RUN、CMD 和 ENTRYPOINT 指令都可以用来执行具体的命令
  • RUN 指令是在 Docker 镜像构建时发挥作用, 可以使用多个该命令, 且执行结果会记录到镜像中
  • CMD 和 ENTYPOINT 指令是在容器启动时自动执行, 均只有最后一个该指令有效, 且均可以在 docker run 中被覆盖
  • ENTRYPOINT 指令和 CMD 的区别在于使用 ENTRYPOINT 时 CMD 指令会被作为其默认参数, 而用户也可以在启动容器时通过覆盖 CMD 指令来输入参数; 此外, 这也意味着 ENTRYPOINT 指令的内容不易被用户命令覆盖

Docker Compose

Docker Compose 的意义在于结合多个 Docker 容器,比如搭建一个网站(Linux + Nginx + Mysql + php)

这里再以一个实际的例子回顾,这是我的本地的目录

  • test
    • mqsql
      • app.sql
      • Dockerfile
    • nginx
      • default.conf
      • Dockerfile
    • php1
      • src
        • index.php
      • Dockerfile
    • php2
      • src
      • Dockerfile
    • docker-compose.yml

执行命令的路径与 docker-compose 同级

  • docker-compose 文件:
version: '3'
# Linux + Nginx + Mysql + PHP
services:
  nginx:
    build: ./nginx
    container_name: "nginx"
    ports:
      - 8080:80
    restart: always
    cap_add:
        - SYS_ADMIN
    depends_on:
      - php1
      - php2
    networks:
      default:
      my_net:
        ipv4_address: 172.2.0.3
  php1:
    build: ./php1
    container_name: nssctf
  php2:
    build: ./php2
    container_name: xenny
  mysql:
    build: ./mysql
    container_name: mysql
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=app
    command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --sql-mode='' --max-execution-time=1000

networks:
  my_net:
    driver: bridge
    internal: true
    ipam:
      config:
        - subnet: 172.2.0.0/16

关于内容的具体细节依然可以看这篇文章,我就不赘述了

posted @ 2022-09-08 01:30  筱团  阅读(191)  评论(0编辑  收藏  举报