Docker基础入门


一、解决的问题

由于不同的机器有不同的操作系统,以及不同的库和组件,在将一个应用部署到多台机器上需要进行大量的环境配置操作。

Docker 主要解决环境配置问题,它是一种虚拟化技术,对进程进行隔离,被隔离的进程独立于宿主操作系统和其它隔离的进程。使用 Docker 可以不修改应用程序代码,不需要开发人员学习特定环境下的技术,就能够将现有的应用程序部署在其它机器上。


二、与虚拟机的比较

虚拟机也是一种虚拟化技术,它与 Docker 最大的区别在于它是通过模拟硬件,并在硬件上安装操作系统来实现。


1.启动速度

启动虚拟机需要先启动虚拟机的操作系统,再启动应用,这个过程非常慢;

而启动 Docker 相当于启动宿主操作系统上的一个进程。

2.占用资源

虚拟机是一个完整的操作系统,需要占用大量的磁盘、内存和 CPU 资源,一台机器只能开启几十个的虚拟机。

而 Docker 只是一个进程,只需要将应用以及相关的组件打包,在运行时占用很少的资源,一台机器可以开启成千上万个 Docker。

三、Docker优势

除了启动速度快以及占用资源少之外,Docker 具有以下优势:

1.更容易迁移

提供一致性的运行环境。已经打包好的应用可以在不同的机器上进行迁移,而不用担心环境变化导致无法运行。

2.更容易维护

使用分层技术和镜像,使得应用可以更容易复用重复的部分。复用程度越高,维护工作也越容易。

3.更容易扩展

可以使用基础镜像进一步扩展得到新的镜像,并且官方和开源社区提供了大量的镜像,通过扩展这些镜像可以非常容易得到我们想要的镜像。

四、Docker使用场景

1.持续集成

持续集成指的是频繁地将代码集成到主干上,这样能够更快地发现错误。

Docker 具有轻量级以及隔离性的特点,在将代码集成到一个 Docker 中不会对其它 Docker 产生影响。

2.提供可伸缩的云服务

根据应用的负载情况,可以很容易地增加或者减少 Docker。

3.搭建微服务架构

Docker 轻量级的特点使得它很适合用于部署、维护、组合微服务。

五、镜像与容器

镜像是一种静态的结构,可以看成面向对象里面的类,而容器是镜像的一个实例。

镜像包含着容器运行时所需要的代码以及其它组件,它是一种分层结构,每一层都是只读的(read-only layers)。构建镜像时,会一层一层构建,前一层是后一层的基础。镜像的这种分层存储结构很适合镜像的复用以及定制。

构建容器时,通过在镜像的基础上添加一个可写层(writable layer),用来保存着容器运行过程中的修改。


六、Docker环境准备

安装docker

centos 安装 docker

# 卸载旧版
yum remove docker \
  docker-client \
  docker-client-latest \
  docker-common \
  docker-latest \
  docker-latest-logrotate \
  docker-logrotate \
  docker-engine

# 1. 阿里云一键安装
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
# 2. 也可以使用国内 daocloud 一键安装命令
curl -sSL https://get.daocloud.io/docker | sh

# If you would like to use Docker as a non-root user, you should now consider
# adding your user to the "docker" group
sudo usermod -aG docker your-user

sudo systemctl start docker

ubuntu/debian 安装 docker

# # 卸载旧版
apt-get remove docker docker-engine docker.io containerd runc

# 1. 阿里云一键安装
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
# 2. 也可以使用国内 daocloud 一键安装命令
curl -sSL https://get.daocloud.io/docker | sh

# If you would like to use Docker as a non-root user, you should now consider
# adding your user to the "docker" group
sudo usermod -aG docker your-user

windows 安装 docker

系统说明:

1.win7/win8等需要利用 docker toolbox 来安装,下载地址:http://mirrors.aliyun.com/docker-toolbox/windows/docker-toolbox/
2.建议win10及以上系统,开启Hyper-V;
程序和功能->启用或关闭Windows功能->勾选Hyper-V->确定,[下载地址]

MacOS 安装 docker

  • 使用 Homebrew 安装
brew cask install docker
  • 手动下载安装

下载地址: [https://www.docker.com/get-started]

docker 镜像加速

更换源, 位置一般为: ~/.docker/daemon.json/etc/docker/daemon.json

网易:https://hub-mirror.c.163.com/
阿里云:https://7zcq3x2s.mirror.aliyuncs.com
七牛云加速器:https://reg-mirror.qiniu.com

{ 
  "registry-mirrors": [ 
    "https://7zcq3x2s.mirror.aliyuncs.com" 
  ] 
}

docker-compose 安装

linux环境

# github中国香港
# https://pd.zwc365.com/seturl/ 
# https://g.ioiox.com/
DC_PROXY="https://pd.zwc365.com/seturl/"
DC_VERSION=`curl https://github.com/docker/compose/releases \
  | grep releases/tag \
  | head -n1 \
  | awk -F ">" '{print $2}' \
  | awk -F "<" '{print $1}'`
echo ${DC_VERSION}
curl -L "${DC_PROXY}https://github.com/docker/compose/releases/download/${DC_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
# 测试查看版本
docker-compose --version

七、Docker基础命令和使用案例

Docker基础命令

# 即从注册服务器 registry.hub.docker.com 中的 alpine 仓库来下载标记为 3.7 的镜像
docker pull registry.hub.docker.com/alpine:3.7

# 存出镜像
docker save -o alpine-3.7.tar alpine:3.7

# 载入镜像
docker load --input alpine-3.7.tar
docker load < alpine-3.7.tar

# 导出容器
docker ps -a
#-- CONTAINER ID  IMAGE       COMMAND      CREATED     STATUS                   PORTS        NAMES
#-- 7691a814370e  alpine:3.7  "/bin/sh"  36 hours ago  Exited (0) 21 hours ago               test
docker export 7691a814370e > alpine-3.7.tar

# 导入容器快照
cat alpine-3.7.tar | docker import - test/alpine:v1.0
docker import http://example.com/exampleimage.tgz example/imagerepo

# 注:用户既可以使用 docker load 来导入镜像存储文件到本地镜像库,也可以使用 docker import
#     来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息
#     (即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文
#     件导入时可以重新指定标签等元数据信息。

# 进入容器或执行命令
docker exec -it $cname/$id $command

## 删除所有容器
docker rm $(docker ps -a -q)

# 删除所有镜像
docker rmi (docker images | grep none | awk '{print3}' | sort -r)

# 一个容器连接到另一个容器
# sonar容器连接到mmysql容器,并将mmysql容器重命名为db。
# 这样,sonar容器就可以使用db的相关的环境变量了。
docker run -i -t –name sonar -d -link mmysql:db tpires/sonar-server

# 构建自己的镜像
# 如Dockerfile在当前路径:docker build -t xx/gitlab .
docker build --no-cache=ture -t <镜像名> 

# ----- 查看容器的 -----
# 容器的状态
docker inspect -f '{{.State.Running}}' $cid
# 容器的IP 
docker inspect –format='{{.NetworkSettings.IPAddress}}' 87d22e81a3a3 

Docker tags

#!/bin/bash

API="https://registry.hub.docker.com/v1/repositories"
DEFAULT_NAME="nginx"
DEFAULT_TIMEOUT=3

function Usage(){
cat << HELP

Usage: docker-tags NAME[:TAG]

docker-tags list all tags for docker image on a remote registry.

Example:
    docker-tags (default nginx)
    docker-tags nginx
    docker-tags nginx:1.15.8
    docker search nginx | docker-tags
    docker search nginx | docker-tags :1.15.8
    echo nginx | docker-tags
    echo nginx | docker-tags :1.15.8
HELP
}

ARG=$1
if [[ "$ARG" =~ "-h" ]];then
    Usage
    exit 0
fi

function ParseJson(){
    tr -d '[\[\]" ]' | tr '}' '\n' | awk -F: -v image=$1 '{if(NR!=NF && $3 != ""){printf("%s:%s\n",image,$3)}}'
}

function GetTags(){
    image=$1
    tag=$2
    ret=`curl -s ${API}/${image}/tags`
    tag_list=`echo $ret | ParseJson ${image}`
    if [ -z "$tag" ];then
        echo -e "$tag_list"
    else
        echo -e "$tag_list" | grep -w "$tag"
    fi
}

if [ -z $ARG ] || [[ ${ARG:0:1} == ":" ]];then
    if [ -x /usr/bin/timeout ];then
        images=`timeout $DEFAULT_TIMEOUT` awk '{print $1}' | grep -v "NAME" || echo $DEFAULT_NAME
    else
        images=`awk '{print $1}' | grep -v "NAME"`
    fi
else
    images=`echo $ARG | awk -F: '{print $1}'`
fi
tag=`echo $ARG | awk -F: '{print $2}'`

for i in ${images}
do
    tags=`GetTags $i $tag`
    count=`echo $tags | wc -w`
    if [[ $count -gt 0 ]];then
        echo -e "IMAGE [$i:$tag]:"
        echo -e "$tags"
        echo
    fi
done

Dockerfile指令

# 选择镜像
FROM ubuntu:14.04

# 指定作者
MAINTAINER darebeat@126.com

# 增加标签(labels)来协助通过项目组织镜像,记录授权信息,帮助自动化,或者其他原因
# 一行一行设置标签
LABEL com.example.version="0.0.1-beta"
LABEL vendor="ACME Incorporated"
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""

# 一行设置多个标签
LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"

# 多行设置多个标签
LABEL vendor=ACME\ Incorporated \
      com.example.is-beta= \
      com.example.is-production="" \
      com.example.version="0.0.1-beta" \
      com.example.release-date="2015-02-12"


# 变量参数,可以build时候传入
## docker build --build-arg user=darebeat .
ARG user
ARG CONT_IMG_VER


# 修改环境变量将软件安装目录加到PATH
ENV PATH /usr/local/nginx/bin:$PATH # 将使 CMD [“nginx”] 可以工作

# 也可用于指定通用版本号,这样版本易于维护
ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0}

# 运行系统构建指令
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
RUN apt-get update && apt-get install -y \
    aufs-tools \
    automake \
    build-essential \
    curl \
    dpkg-sig \
    libcap-dev \
    libsqlite3-dev \
    mercurial \
    reprepro \
    ruby1.9.1 \
    ruby1.9.1-dev \
    s3cmd=1.1.* \
 && rm -rf /var/lib/apt/lists/*

# 文件操作
## 对于不需要ADD tar自动提取功能的其他项目(文件,目录),应始终使用COPY
COPY requirements.txt /tmp/
ADD rootfs.tar.xz /
ADD arr[[]0].tar.xz /mydir/

## 避免如下用法,而用wget或curl
ADD http://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all
## 应换成如下用法
RUN mkdir -p /usr/src/things \
    && curl -SL http://example.com/big.tar.xz \
    | tar -xJC /usr/src/things \
    && make -C /usr/src/things all


# 指示容器将监听链接的端口
EXPOSE 27017
EXPOSE 80


# 默认用户
## 变量可以设置默认值
USER ${user:-root}


# 工作目录
WORKDIR /root

# 设置镜像主命令,允许镜像把它作为命令运行(然后使用CMD作为默认标识)
ENTRYPOINT ["ls"]
CMD ["--help"]

# 与辅助脚本组合使用,允许其以类似于上述命令的方式运行,即使启动工具可能需要多于一个步骤
ENTRYPOINT ["/docker-entrypoint.sh"]

# 用于运行你镜像包含中的软件,连同任意参数
CMD [“executable”, “param1”, “param2”…]

编写Dockerfile原则

  • 1.减少镜像层:
    一次RUN指令形成新的一层,尽量Shell命令都写在一行
  • 2.使用.dockerignore文件来排除文件和目录。该文件与 .gitignore 类似
  • 3.避免安装不需要的包,优化镜像大小:
    一次RUN形成新的一层,如果没有在同一层删除,无论文件是否最后删除,都会带到下一层,所以要在每一层清理对应的残留数据,减小镜像大小。
  • 4.每个容器只关心一个问题
    解耦应用为多个容器使水平扩容和复用容器更容易
  • 5.对多行参数排序
    无论何时,以排序多行参数来缓解以后的变化,避免重复的包并且使里列表更容易更新。这也使得PR更容易阅读和审查。
  • 6.减少网络传输时间:
    最好在内部有一个存放软件包的地方,提高镜像构建速度。
  • 7.多阶段进行镜像构建
    如果运行一个项目,根据咱们上面的做法,是直接把代码拷贝到基础镜像里,如果是一个需要预先代码编译的项目呢?例如JAVA语言,如何代码编译、部署在一起完成呢!

1.上面做法需要事先在一个Dockerfile构建一个基础镜像,包括项目运行时环境及依赖库,再写一个Dockerfile将项目拷贝到运行环境中,有点略显复杂了
2.像JAVA这类语言如果代码编译是在Dockerfile里操作,还需要把源代码构建进去,但实际运行时只需要构建出的包,这种把源代码放进去有一定安全风险,并且也增加了镜像体积
3.为了解决上述问题,Docker 17.05开始支持多阶段构建(multi-stage builds),可以简化Dockerfile,减少镜像大小

  • 例如,构建JAVA项目镜像:

    # git clone https://github.com/lizhenliang/tomcat-java-demo
    # cd tomcat-java-demo
    # vi Dockerfile
    FROM maven AS build
    ADD ./pom.xml pom.xml
    ADD ./src src/
    RUN mvn clean package
    
    FROM lizhenliang/tomcat
    RUN rm -rf /usr/local/tomcat/webapps/ROOT
    COPY --from=build target/*.war /usr/local/tomcat/webapps/ROOT.war
    
    # docker build -t demo:v1 .
    # docker container run -d -v demo:v1
    

首先,第一个FROM 后边多了个 AS 关键字,可以给这个阶段起个名字。
然后,第二部分FROM用的我们上面构建的Tomcat镜像,COPY关键字增加了--from参数,用于拷贝某个阶段的文件到当前阶段。这样一个Dockerfile就都搞定了

解决了什么问题: 减少镜像大小,快速部署、快速回滚。减少服务中断时间,同时镜像仓库占用磁盘空间也少了

八、部署实例

docker查看并创建本地公共网络

# 根据需要选择命令执行,如果 deploy 网络已创建,可忽略
docker network ls
docker network inspect deploy
docker netwok rm deploy
docker network create --driver=bridge --subnet=172.10.0.0/16 deploy

docker快速本地部署mysql

# 环境变量
cat << EOF> .env
MYSQL_ROOT_PASSWORD=mysql
MYSQL_USER=canal
MYSQL_PASSWORD=canal 
MYSQL_DATABASE=test
EOF

# docker-compose配置文件 docker-compose.yaml
cat << EOF> docker-compose-mysql.yaml
version: '3'
services:
  mysql:
    image: mysql:5.7
    container_name: mysql
    restart: always
    networks:
      deploy:
        ipv4_address: 172.10.0.4
        aliases:
          - mysql
    ports:
      - "13306:3306"
    env_file:
      - .env
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - ./db:/var/lib/mysql:rw
      - ./src/config/mysqld.cnf:/etc/my.cnf:ro
    tty: true

networks:
  deploy:
    external: true
EOF

# mysql自定义配置
# src/config/mysqld.cnf
mkdir -p src/config
cat << EOF> src/config/mysqld.cnf
[client]
default-character-set=utf8mb4

[mysql]
default-character-set=utf8mb4
max_allowed_packet=500M

[mysqld]
character-set-server=utf8mb4
default_authentication_plugin=mysql_native_password
ft_min_word_len=2
lower_case_table_names=1
default_time_zone=+8:00
max_connections = 512
tmp_table_size = 10240M
max_heap_table_size = 10240M

# 开启binlog,这里按需配置
log-bin=mysql-bin 
binlog-format=ROW # 选择ROW模式
server_id=1 # 配置MySQL replaction需要定义,不要和Canal的slaveId重复
EOF

# docker-compose管理
docker-compose -f docker-compose-mysql.yaml up -d # 启动
docker-compose -f docker-compose-mysql.yaml down -v # 停止并移除实例

# 连接mysql,然后开始你的表演
docker exec -it mysql mysql -uroot -p
# 或者通过宿主机IP连接,也可以通过客户端工具连接
mysql -h 127.0.0.1 -P 13306 -uroot -p

文章作者: darebeat
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 darebeat !
  目录