Docker 容器 - Dockerfile

DockerFile 是一个文本格式的配置文件,用户可以使用 DockerFile 来快速创建自定义的镜像

镜像的定制实际上就是 定制每一层所添加的配置、文件 。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile

一、Dockerfile 基本结构

Dockerfile 分为四部分:基础镜像信息维护者信息镜像操作指令容器启动时执行指令

  • 每条指令创建一层:
    • FROM : 指明所基于的镜像名称
    • MAINTAINER:维护者信息,docker_user 维护(可以不写)。
    • COPY : 从 Docker 客户端的当前目录添加文件镜像操作 指令。
    • RUN : 构建镜像时 执行 make 命令,每运行一条 RUN 指令,镜像就添加新的一层,并提交(添加可写层)。
    • CMD : 指定在容器中运行什么命令,用来指定运行容器时的操作命令

Docker 镜像由只读层组成,每个只读层代表一个 Dockerfile 指令。这些层是堆叠的,每个层都是上一层的变化的增量

Docker 可以通过 读取 Dockerfile 指令 来自动构建镜像

# 在一个空目录下,新建一个名为 Dockerfile 文件
mkdir /www/	#/www这个目录是自己创建的,通过创建空目录,在此目录下创建文件
vim /www/Dockerfile

# 编辑 Dockerfile
FROM nginx:1.27.4

# 维护者,可以省略
MAINTAINER kendra kendra@docker.com

# 启动容器
RUN mkdir /usr/share/nginx/html -p
RUN echo "hello docker" > /usr/share/nginx/html/index.html

# 构建镜像 . : 根据当前上下文环境构建
docker build -t mynginx:v1.0 .

# 运行
docker run --rm -it mynginx:v1.0 /bin/bash
[root@Docker-kd ]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
tomcat       11.0.5    e8c9a002d37b   7 weeks ago    467MB
nginx        1.27.4    4cad75abc83d   2 months ago   192MB
mysql        8.4.4     4a8a163431d3   3 months ago   769MB
busybox      latest    ff7a7936e930   7 months ago   4.28MB
[root@Docker-kd ]# ls /
afs  boot  etc   lib    media  opt   root  sbin  sys  usr
bin  dev   home  lib64  mnt    proc  run   srv   tmp  var
[root@Docker-kd ]# mkdir /www
[root@Docker-kd ]# cd /www/
[root@Docker-kd www]# ls
[root@Docker-kd www]# vim Dockerfile
[root@Docker-kd www]# cat Dockerfile
FROM nginx:1.27.4
MAINTAINER kendra kendra@docker.com

RUN mkdir /usr/share/nginx/html -p
RUN echo "hello docker" > /usr/share/nginx/html/index.html

编辑界面:w保存,另一个会话即可构建镜像、运行,测试
在这里插入图片描述

  • 另起一个会话
    • 构建:docker build -t mynginx:v1.0 .
    • 运行:docker run --rm -it mynginx:v1.0 /bin/bash
[root@Docker-kd ~]# cd /www/
[root@Docker-kd www]# ls
Dockerfile
# 构建
[root@Docker-kd www]# docker build -t mynginx:v1.0 .
[+] Building 0.9s (7/7) FINISHED                                           docker:default
 => [internal] load build definition from Dockerfile                                 0.0s
 => => transferring dockerfile: 245B                                                 0.0s
 => WARN: MaintainerDeprecated: Maintainer instruction is deprecated in favor of us  0.0s
 => [internal] load metadata for docker.io/library/nginx:1.27.4                      0.0s
 => [internal] load .dockerignore                                                    0.0s
 => => transferring context: 2B                                                      0.0s
 => [1/3] FROM docker.io/library/nginx:1.27.4                                        0.0s
 => [2/3] RUN mkdir /usr/share/nginx/html -p                                         0.3s
 => [3/3] RUN echo "hello docker" > /usr/share/nginx/html/index.html                 0.5s
 => exporting to image                                                               0.0s
 => => exporting layers                                                              0.0s
 => => writing image sha256:7c6af446d495a180d79138a4abfc5c2b99d9b0f639a9e7e3c6bc8b8  0.0s
 => => naming to docker.io/library/mynginx:v1.0                                      0.0s

 1 warning found (use docker --debug to expand):
 - MaintainerDeprecated: Maintainer instruction is deprecated in favor of using label (line 2)

[root@Docker-kd www]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
mynginx      v1.0      7c6af446d495   3 minutes ago   192MB
tomcat       11.0.5    e8c9a002d37b   7 weeks ago     467MB
nginx        1.27.4    4cad75abc83d   2 months ago    192MB
mysql        8.4.4     4a8a163431d3   3 months ago    769MB
busybox      latest    ff7a7936e930   7 months ago    4.28MB
# 运行
[root@Docker-kd www]# docker run --rm -it mynginx:v1.0 /bin/bash
root@741204f40a3d:/# pwd
/
root@741204f40a3d:/# ls
bin   docker-entrypoint.d   home   media  proc	sbin  tmp
boot  docker-entrypoint.sh  lib    mnt	  root	srv   usr
dev   etc		    lib64  opt	  run	sys   var
root@741204f40a3d:/# cd /usr/share/nginx/html/
root@741204f40a3d:/usr/share/nginx/html# ls
50x.html  index.html
root@741204f40a3d:/usr/share/nginx/html# exit
exit

二、Dockerfile 指令详解

2.1 FROM

FROM 指令必须是 Dockerfile 中非注释行的第一个指令,即一个 Dockerfile 从FROM 语句开始,如果FROM语句没有指定镜像标签,则默认使用latest标签

FROM 指令用于为镜像文件构建过程指定基础镜像

FROM可以在一个 Dockerfile 中出现多次,如果有需求在一个 Dockerfile 中创建多个镜像。
如果FROM语句没有指定镜像标签,则默认使用latest标签

  • 命令格式如下:
FROM [--platform=<platform>] <image> [AS <name>]
或者
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
或者
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

FROM <image>[:<tag>]
FROM busybox:latest

2.2 MAINTAINER

指定维护者信,可以不写。

  • 命令格式如下:
MAINTAINER <authtor's detail>

例如:
FROM busybox:latest
MAINTAINER "jock <jock@docker.com>"

2.3 COPY

用于从 docker 宿主机复制文件或者目录至创建的新镜像中的路径下,目标路径不存在时,会自动创建

  • 命令格式如下:
COPY <src>... <dest>
或
COPY ["<src>",... "<dest>"]

在这里插入图片描述

# COPY文件:
FROM busybox:latest
MAINTAINER "jock <jock@docker.com>"
COPY index.html /data/web/html/

#构建:
# 在dockerfile同级目录下准备好index.html文件
[root@localhost ~]# vim index.html
<h1>Busybox httpd server</h1>
[root@localhost ~]# docker build -t busyboxhttpd:v0.1 ./

#运行:
[root@localhost ~]# docker run --name web1 --rm busyboxhttpd:v0.1 cat /data/web/html/index.html
<h1>Busybox httpd server</h1>
# COPY 目录
vim index.html
<h1>Busybox httpd server</h1>
FROM busybox:latest
MAINTAINER "jock <jock@docker.com>"
COPY index.html /data/web/html/
COPY yum.repos.d /etc/yum.repos.d/

注:如果是复制目录,则其内部文件或子目录会被递归复制,但目录自身不会被复制;需要把复制目录名字也写在容器中要复制的路径下

#构建:
# 在dockerfile同级目录下准备好index.html文件
[root@localhost ~]# vim index.html
<h1>Busybox httpd server</h1>
[root@localhost ~]# docker build -t busyboxhttpd:v0.1 ./

#运行:
[root@localhost ~]# docker run --name web1 --rm busyboxhttpd:v0.2 ls /etc/yum.repos.d/
along.repo
docker-ce.repo
epel-release-latest-7.noarch.rpm
epel-testing.repo
epel.repo

当使用本地目录为源目录时,推荐使用 COPY

2.4 ADD

ADD 指令类似于COPY指令,ADD支持使用TAR文件和URL路径,该命令将复制指定的路径下的内容到容器中的路径下

  • 命令格式如下:
ADD <src> .. <dest>
或
ADD ["<src>".. "<dest>"]

在这里插入图片描述

与COPY的区别
1、Dockerfile 中的 COPY 指令和 ADD 指令都可以将主机上的资源复制或加入到容器镜像中,都是在构建镜像的过程中完成的。
2、COPY 指令和 ADD 指令的区别在于是否支持从远程 URL 获取资源
COPY 指令只能从执行 docker build 所在的主机上读取资源并复制到镜像中
而 ADD 指令还支持通过 URL 从远程服务器读取资源并复制到镜像中
3、满足同等功能的情况下,推荐使用 COPY 指令。ADD 指令更擅长读取本地 tar 文件并解压缩
4、当要读取 URL 远程资源的时候,并不推荐使用 ADD 指令,而是建议使用 RUN 指令,在 RUN 指令中执行 wget 或 curl 命令。

  • COPY 网上路径(URL)的tar包
# COPY 网上路径(URL)的tar包
FROM busybox:latest
MAINTAINER "jock <jock@docker.com>"
COPY index.html /data/web/html/
COPY yum.repos.d /etc/yum.repos.d/
ADD http://nginx.org/download/nginx-1.15.8.tar.gz /usr/local/src/
#注:ADD 的 是网上的nginx下载路径

#构建:
[root@localhost img1]# docker build -t busyboxhttpd:v0.3 ./

#运行:
[root@localhost ~]# docker run --name web1 --rm busyboxhttpd:v0.3 ls
/usr/local/src
nginx-1.15.8.tar.gz
  • COPY 本地的路径的tar包
FROM busybox:latest
MAINTAINER "jock <jock@docker.com>"
COPY index.html /data/web/html/
COPY yum.repos.d /etc/yum.repos.d/
ADD nginx-1.15.8.tar.gz /usr/local/src/

#构建:
# 在dockerfile同级目录下准备好yum.repos.d 目录
[root@localhost img1]# wget http://nginx.org/download/nginx-
1.15.8.tar.gz
[root@localhost img1]# docker build -t busyboxhttpd:v0.4 ./

#运行:
[root@localhost ~]# docker run --name web1 --rm busyboxhttpd:v0.4 ls
/usr/local/src /usr/local/src/nginx-1.15.8
/usr/local/src:
nginx-1.15.8

/usr/local/src/nginx-1.15.8:
CHANGES
CHANGES.ru
LICENSE
README
auto
conf
configure
contrib
html
man
src

2.5 WORKDIR

用于为 Dockerfile 中所有的 RUN、CMD、ENTRYPOINT、COPY 和 ADD 指定设定工作目录

  • 命令格式如下:
WORKDIR <dirpath>

在 Dockerfile 文件中,WORKDIR 指令可出现多次,其路径也可以为相对路径,不过,其是相对此前一个 WORKDIR 指令指定的路径。另外,WORKDIR 也可调用由ENV指定定义的变量

eg.
FROM busybox:latest
MAINTAINER "jock <jock@docker.com>"

COPY index.html /data/web/html/
COPY yum.repos.d /etc/yum.repos.d/

WORKDIR /usr/local/

ADD nginx-1.15.8.tar.gz ./src/

2.6 VOLUME

用于在镜像中创建一个 挂载点 目录,以挂载 Docker host 上的卷或其它容器上的卷

  • 命令格式如下:
VOLUME <mountpoint>
或
VOLUME ["<mountpoint>"]

注:如果挂载点目录路径下此前在文件存在,docker run命令会在卷挂载完成后将此前的所有文件复制到新挂载的卷中

# eg.

FROM busybox:latest
MAINTAINER "jock <jock@docker.com>"

COPY index.html /data/web/html/
COPY yum.repos.d /etc/yum.repos.d/

WORKDIR /usr/local/
ADD nginx-1.15.8.tar.gz ./src/

VOLUME /data/mysql
# 构建:
[root@localhost ~]# docker build -t busyboxhttpd:v0.5 ./

# 运行:[root@localhost ~]# docker run --name web1 --rm -it busyboxhttpd:v0.5
/bin/sh
/usr/local #
--- 另打开一个终端,查询存储卷
[root@localhost ~]# docker inspect -f {{.Mounts}} web1
[{volume
b788b8a50d69953e2b086b3b54ba683154647319a481246cb7ab2ff927b21372
/var/lib/docker/volumes/b788b8a50d69953e2b086b3b54ba683154647319a48124
6cb7ab2ff927b21372/_data /data/mysql local true }]

2.7 EXPOSE

用于为容器打开指定要监听的端口以实现与外部通信

  • 命令格式如下:
EXPOSE <port>[/ <protocol>] [<port>[/ <protocol>] ....

在这里插入图片描述

FROM busybox:latest
MAINTAINER "jock <jock@docker.com>"

COPY index.html /data/web/html/
COPY yum.repos.d /etc/yum.repos.d/

WORKDIR /usr/local/
ADD nginx-1.15.8.tar.gz ./src/

VOLUME /data/mysql

EXPOSE 80/tcp

注:就算dockerfile 中有EXPOSE 指令暴露端口,但是不是真正的暴露;需要在启动容器时,使用 -p 选项真正的暴露端口

# 构建
[root@localhost img1]# docker build -t busyboxhttpd:v0.6 ./

# 运行
[root@localhost ~]# docker run --name web1 -P --rm -it
busyboxhttpd:v0.6 /bin/httpd -f -h /data/web/html
--- 另打开一个终端,验证httpd 服务的80端口
[root@localhost ~]# docker inspect -f {{.NetworkSettings.IPAddress}}
web1 #查询容器的IP
172.17.0.2
[root@localhost ~]# curl 172.17.0.2:80
<h1>Busybox httpd server</h1>
--- 在宿主机通过暴露的端口访问httpd 服务
[root@localhost ~]# docker port web1
80/tcp -> 0.0.0.0:32768
[root@localhost ~]# curl 127.0.0.1:32768
<h1>Busybox httpd server</h1>

2.8 ENV

用于为镜像定义所需的环境变量,并可被 Dockerfile 文件中位于其后的其它指令(如 ENV、ADD、COPY 等)所调用,调用格式为$variable_ name 或 ${variable_ name}

  • 命令格式如下:
ENV <key> <value>
或
ENV <key>=<value> ...

在这里插入图片描述

FROM busybox:latest
MAINTAINER "jock <jock@docker.com>"

ENV DOC_ROOT=/data/web/html/ \
	WEB_SERVER_PACKAGE="nginx-1.15.8"

COPY index.html ${DOC_ROOT}
COPY yum.repos.d /etc/yum.repos.d/

WORKDIR /usr/local/
ADD ${WEB_SERVER_PACKAGE}.tar.gz ./src/

VOLUME /data/mysql

EXPOSE 8080:80/tcp
# 构建
[root@localhost ~]# docker build -t busyboxhttpd:v0.7 ./

# 运行
[root@localhost ~]# docker run --name web1 -P --rm -it
busyboxhttpd:v0.7 ls /usr/local/src /data/web/html
/data/web/html:
index.html

/usr/local/src:
nginx-1.15.8
--- 也可以使用printenv 查看变量验证
[root@localhost ~]# docker run --name web1 --rm -it busyboxhttpd:v0.7
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
DOC_ROOT=/data/web/html/
WEB_SERVER_PACKAGE=nginx-1.15.8
--- 在启动容器时,使用docker run -e 设置修改变量
[root@localhost ~]# docker run --name web1 -e
WEB_SERVER_PACKAGE=nginx-1.15.7 --rm -it busyboxhttpd:v0.7 printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
WEB_SERVER_PACKAGE=nginx-1.15.7
DOC_ROOT=/data/web/html/

2.9 RUN

用于指定 docker build 过程中运行的程序,其可以是任何命令

  • 命令格式如下:
RUN <command>
或
RUN ["<executable>", "<param1>", "<param2>"]

在这里插入图片描述

FROM busybox:latest
MAINTAINER "jock <jock@docker.com>"

ENV DOC_ROOT=/data/web/html/ \
	WEB_SERVER_PACKAGE="nginx-1.15.8.tar.gz"

COPY index.html ${DOC_ROOT}
COPY yum.repos.d /etc/yum.repos.d/

WORKDIR /usr/local/
ADD http://nginx.org/download/${WEB_SERVER_PACKAGE} ./src/

VOLUME /data/mysql

EXPOSE 8080:80/tcp

RUN cd ./src && \
	tar -xf ${WEB_SERVER_PACKAGE}
# 构建
[root@localhost ~]# docker build -t busyboxhttpd:v0.8 ./

# 运行
[root@localhost ~]# docker run --name web1 -P --rm -it
busyboxhttpd:v0.7 ls /usr/local/src
nginx-1.15.8

2.10 CMD

类似于 RUN 指令,CMD 指令也可用于运行任何命令或应用程序

  • 不同点:

    • RUN 指令运行于映像文件构建过程中,而 CMD 指令运行于基于 Dockerfile 构建出的新映像文件启动一个容器时。
    • CMD 指令首要目的在于为启动的容器指定默认要运行的程序,且其运行结束后,容器也将终止。不过,CMD 指定的命令其可以被 docker run 的命令行选项所覆盖。
    • 在 Dockerfile 中可存在多个 CMD 指令,但仅最后一个会生效
  • 命令格式如下:

CMD <command>
或
CMD ["<executable>","<param1>","<param2>"]
或
CMD ["<param1>","<param2>"]

注:
前两种语法格式的意义同 RUN。
第三种则用于为 ENTRYPOINT 指令提供默认参数。
JSON 数组中,要使用双引号,单引号会出错。

FROM busybox
MAINTAINER "jock <jock@docker.com>"

ENV WEB_DOC_ROOT="/data/web/html"

RUN mkdir -p ${WEB_DOC_ROOT} && \
	echo "<h1>Busybox httpd server</h1>" > ${WEB_DOC_ROOT}/index.html

CMD /bin/httpd -f -h ${WEB_DOC_ROOT}
# 构建
docker build -t busyboxhttpd:v1.1 ./

# 运行
[root@localhost ~]# docker run --name web2 --rm -d busyboxhttpd:v1.1
20aa07198943887db51173e245392f75e3654525cb32242f2b04f0b3e007e47d
[root@localhost ~]# docker inspect -f {{.NetworkSettings.IPAddress}}
web2
172.17.0.2
[root@localhost ~]# curl 172.17.0.2
<h1>Busybox httpd server</h1>

使用CMD定义的命令,在启动容器时,会被后面追加的指令覆盖;与下面ENTRYPOINT 指令对比
[root@localhost ~]# docker kill web2
web2
[root@localhost ~]# docker run --name web2 --rm busyboxhttpd:v1.1 ls /
bin
data
dev
etc
[root@localhost ~]# curl 172.17.0.2 被ls /覆盖,所以没有执行httpd服务

2.11 ENTRYPOINT

类似 CMD 指令的功能,用于为容器指定默认运行程序,从而使得容器像是一个单独的可执行程序。

与 CMD 不同的是,由 ENTRYPOINT 启动的程序不会被 docker run 命令行指定的参数所覆盖,而且,这些命令行参数会被当作参数传递给ENTRYPOINT 指定指定的程序。

不过,docker run 命令的 --entrypoint 选项的参数可覆盖 ENTRYPOINT 指令指定的程序

  • 命令格式如下:
ENTRYPOINT <command>
ENTRYPOINT ["<executable>", "<param1>", "<param2>"]

注:
docker run 命令传入的命令参数会覆盖 CMD 指令的内容并且附加到 ENTRYPOINT 命令最后做为其参数使用。
Dockerfile 文件中也可以存在多个 ENTRYPOINT 指令,但仅有最后一个会生效

FROM busybox
MAINTAINER "jock <jock@docker.com>"

ENV WEB_DOC_ROOT="/data/web/html"

RUN mkdir -p ${WEB_DOC_ROOT} && \
	echo "<h1>Busybox httpd server</h1>" > ${WEB_DOC_ROOT}/index.html
	
ENTRYPOINT /bin/httpd -f -h ${WEB_DOC_ROOT}
# 构建
[root@localhost ~]# docker build -t busyboxhttpd:v1.2 ./

# 运行
[root@localhost ~]# docker run --name web2 --rm busyboxhttpd:v1.2 ls /
发现是不会执行ls / 这个命令;仍然执行的是ENTRYPOINT中设置的命令;与上面CMD 指令对比;
[root@localhost ~]# curl 172.17.0.2 #httpd服务仍然执行,没有被ls / 指令覆盖
<h1>Busybox httpd server</h1>

三、Dockerfile 创建镜像与模板

3.1 Dockerfile 镜像

编写完成 Dockerfile 之后,可以通过 docker build 命令来创建镜像

基本的格式为:

docker build [选项] 内容路径

该命令将读取指定路径下(包括子目录)的 Dockerfile,并将该路径下的所有内容发送给Docker服务端,由服务端来创建镜像

# 如果使用非内容路径下的Dockerfile,可以通过-f选项来指定其路径
docker build -t lagou/ubuntu:v1 .
#docker build 最后的 . 号,其实是在指定镜像构建过程中的上下文环境的目录

3.2 镜像管理

Docker 镜像由一系列层组成。每层代表镜像的 Dockerfile 中的一条指令。除最后一层外的每一层都是只读的

# 该 Dockerfile 包含四个命令,每个命令创建一个层
FROM ubuntu:18.04	#从 ubuntu:18.04 镜像创建只读层
COPY . /app		#从 Docker 客户端的当前目录添加一些文件
RUN make /app	#使用命令构建应用程序 make
CMD python /app/app.py	#最后一层指定在容器中运行什么命令

运行镜像并生成容器时,可以在基础层之上添加一个新的可写层(“容器层”)。对运行中的容器所做的所有更改(例如写入新文件,修改现有文件和删除文件)都将写入此可写容器层

容器和镜像之间的主要区别是可写顶层

在容器中添加新数据或修改现有数据的所有写操作都存储在此可写层中。删除容器后,可写层也会被删除。基础镜像保持不变。

因为每个容器都有其自己的可写容器层,并且所有更改都存储在该容器层中,所以多个容器可以共享对同一基础镜像的访问,但具有自己的数据状态

  • 查看镜像的分层信息
docker history 镜像ID
  • 要查看正在运行的容器的大致大小,可以使用以下 docker ps -s 命令
    • size :用于每个容器的可写层的数据量(在磁盘上)
    • virtual size :容器使用的只读图像数据加上容器的可写层使用的数据量size
      多个容器可以共享部分或全部只读图像数据。从同一图像开始的两个容器共享100%的只读数据,而具有不同图像的两个容器(具有相同的层)共享这些公共层
# 可以通过Docker仓库来传输我们的镜像,也可以通过文件模式
docker save 镜像ID -o xxxx.tar 或(docker save xxxx > xxxx.tar)

docker load -i xxxx.tar 或docker (docker load < xxxx.tar)

docker diff 容器ID

docker commit 容器ID openlab/testimage:version4 # 直接保存容器

docker commit --change='CMD ["apachectl", "-DFOREGROUND"]' -c "EXPOSE 80" 容器ID openlab/testimage:version4 # 将正在运行的容器添加几个层之后再保存】

3.3 Dockerfile 模板

通过最小化Dockerfile中RUN 单独命令的数量来减少镜像中的层数。可以通过将多个命令合并为RUN 一行并使用Shell的机制将它们组合在一起来实现此目的

#在镜像中创建两层
RUN apt-get -y update
RUN apt-get install -y python

#在镜像中创建一层
RUN apt-get -y update && apt-get install -y python

3.3.1 Redhat 镜像模板

dockerfile-redhat:

# 依据哪个镜像创建
From centos:7.6.1810

# 指定容器内部使用语言
ENV LANG="en_US.UTF-8"
ENV LC_ALL="en_US.UTF-8"
# 使用亚洲/上海时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# 创建工作目录
RUN mkdir -p /data/apps /data/data /data/logs ; ln -s /data/apps /apps
# 安装字体
RUN yum install -y epel-release
RUN yum groupinstall -y "fonts"
RUN yum install -y kde-l10n-Chinese
# 安装openssl等依赖包
RUN yum install -y openssl openssl-devel
RUN yum install -y crontabs cronolog ntp
# 安装数据库依赖
RUN yum install -y mariadb-libs
RUN ln -s /usr/lib64/mysql/libmysqlclient.so.18
/usr/lib64/libmysqlclient_r.so.16
RUN yum install -y gcc cmake
RUN yum install -y lrzsz telnet net-tools file bind-utils less
RUN yum install -y jq xml2; yum clean all
RUN yum install -y expat-devel apr-devel ghostscript ghostscript-devel
# 运行容器时的默认命令
CMD ["/bin/bash"]
  • 构建命令:
docker build -f dockerfile-redhat -t lagou/centos/7.6/centos .
docker run --rm -it lagou/centos/7.6/centos

# 使用 Red Hat 9 作为基础镜像
FROM registry.access.redhat.com/ubi9/ubi:latest

# 设置时区为亚洲/上海
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ >
/etc/timezone

# 安装中文语言包
RUN dnf install -y kde-l10n-Chinese

# 安装 openssl 和其他依赖包
RUN dnf install -y openssl openssl-devel

# 安装 MySQL 8.4.4 的依赖
RUN dnf install -y \
libaio \
numactl-libs \
libtirpc \
libedit \
ncurses-compat-libs \
wget \
tar

# 下载并安装 MySQL 8.4.4
RUN wget https://dev.mysql.com/get/Downloads/MySQL-8.4/mysql-8.4.4-
linux-glibc2.28-x86_64.tar.gz -O /tmp/mysql.tar.gz && \
tar -xzf /tmp/mysql.tar.gz -C /opt && \
mv /opt/mysql-8.4.4-linux-glibc2.28-x86_64 /opt/mysql && \
rm -f /tmp/mysql.tar.gz

# 设置 MySQL 环境变量
ENV PATH=/opt/mysql/bin:$PATH

# 创建 MySQL 数据目录
RUN mkdir -p /var/lib/mysql && \
chown -R mysql:mysql /var/lib/mysql

# 初始化 MySQL
RUN mysqld --initialize-insecure --user=mysql --basedir=/opt/mysql --
datadir=/var/lib/mysql

# 暴露 MySQL 默认端口
EXPOSE 3306

# 设置容器启动时运行的命令
CMD ["mysqld", "--user=mysql"]
  • 构建和运行命令:
docker build -t redhat9-mysql8.4.4 .
docker run -d --name mysql-container -p 3306:3306 redhat9-mysql8.4.4

3.3.2 JDK 镜像模板

dockerfile-jdk:

# 使用 Red Hat 9 作为基础镜像
FROM registry.access.redhat.com/ubi9/ubi:latest

# 设置时区为亚洲/上海
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ >
/etc/timezone

# 安装必要的工具
RUN dnf install -y \
wget \
tar \
gzip \
&& dnf clean all

# 下载并安装 JDK 21
# 注意:请根据实际需求替换 JDK 21 的下载链接
RUN wget
https://download.java.net/java/GA/jdk21/fd2272bbf8e04c3dbaee1377009041
6c/35/GPL/openjdk-21_linux-x64_bin.tar.gz -O /tmp/jdk21.tar.gz && \
tar -xzf /tmp/jdk21.tar.gz -C /opt && \
mv /opt/jdk-21 /opt/jdk && \
rm -f /tmp/jdk21.tar.gz

# 设置 JDK 环境变量
ENV JAVA_HOME=/opt/jdk
ENV PATH=$JAVA_HOME/bin:$PATH

# 验证安装
RUN java -version

# 设置工作目录
WORKDIR /app

# 默认启动命令(可以根据需要修改)
CMD ["java", "-version"]
  • 构建命令:
docker build -t redhat9-jdk21 .
docker run -it --rm redhat9-jdk21

3.3.3 Tomcat 镜像模板

dockerfile-jdk:

# 使用 Red Hat 9 作为基础镜像
FROM registry.access.redhat.com/ubi9/ubi:latest

# 设置时区为亚洲/上海
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ >
/etc/timezone

# 安装必要的工具
RUN dnf install -y \
wget \
tar \
gzip \
&& dnf clean all

# 下载并安装 JDK 21
RUN wget
https://download.java.net/java/GA/jdk21/fd2272bbf8e04c3dbaee1377009041
6c/35/GPL/openjdk-21_linux-x64_bin.tar.gz -O /tmp/jdk21.tar.gz && \
	tar -xzf /tmp/jdk21.tar.gz -C /opt && \
	mv /opt/jdk-21 /opt/jdk && \
	rm -f /tmp/jdk21.tar.gz

# 设置 JDK 环境变量
ENV JAVA_HOME=/opt/jdk
ENV PATH=$JAVA_HOME/bin:$PATH

# 验证 JDK 安装
RUN java -version

# 下载并安装 Tomcat 11.0.5
RUN wget https://archive.apache.org/dist/tomcat/tomcat-
11/v11.0.5/bin/apache-tomcat-11.0.5.tar.gz -O /tmp/tomcat.tar.gz && \
	tar -xzf /tmp/tomcat.tar.gz -C /opt && \
	mv /opt/apache-tomcat-11.0.5 /opt/tomcat && \
	rm -f /tmp/tomcat.tar.gz
	
# 设置 Tomcat 环境变量
ENV CATALINA_HOME=/opt/tomcat
ENV PATH=$CATALINA_HOME/bin:$PATH

# 创建 Tomcat 用户和组
RUN groupadd -r tomcat && \
	useradd -r -g tomcat -d $CATALINA_HOME -s /bin/false tomcat && \
	chown -R tomcat:tomcat $CATALINA_HOME

# 暴露 Tomcat 默认端口
EXPOSE 8080

# 设置工作目录
WORKDIR $CATALINA_HOME

# 切换为 Tomcat 用户
USER tomcat

# 启动 Tomcat
CMD ["catalina.sh", "run"]
  • 构建:
# 构建
docker build -t redhat9-tomcat11.0.5 .

# 运行
docker run -d --name tomcat-container -p 8080:8080 redhat9-
tomcat11.0.5

访问:
打开浏览器,访问 http://localhost:8080 ,即可看到 Tomcat 的默认页面

  • 注意事项:
    • 如果需要部署自定义的 WAR 文件,可以将 WAR 文件复制到 /opt/tomcat/webapps 目录。
    • 如果需要持久化 Tomcat 的日志或配置文件,可以使用 Docker 卷( volumes )。
    • 如果需要调整 Tomcat 的配置,可以修改 /opt/tomcat/conf 目录下的配置文件。

四、Docker 数据持久化

  1. 创建一个卷,待后边使用
docker volume create test_volume
  1. 分别启动2个容器挂在上卷
# 在2个终端窗口启动2个容器
docker run -it --rm -v test_volume:/test nginx:1.27.4 /bin/bash
docker run -it --rm -v test_volume:/test nginx:1.27.4 /bin/bash
cd /test;
touch a.txt
ls /test

# 在两个容器中我们均可以看到我们创建的文件,这样我们就可以做到了在多个容器之间实现数据共享

挂载在容器/test 目录内创建

Docker 不支持容器内安装点的相对路径。 多个容器可以在同一时间段内使用相同的卷。如果两个容器需要访问共享数据,这将很有用
注意:如果宿主机上的目录可以不存在,会在启动容器的时候创建

五、案例实战

5.1 构建nginx服务镜像

[root@docker ~]# ls
anaconda-ks.cfg  elasticsearch.yml  index3.html          redis:7.4.2.tar.gz
centos_7.tar.gz  index2.html        nginx.1.27.4.tag.gz
[root@docker ~]# docker load -i centos_7.tar.gz 
174f56854903: Loading layer  211.7MB/211.7MB
Loaded image: centos:7
[root@docker ~]# docker images
REPOSITORY      TAG       IMAGE ID       CREATED        SIZE
redis           latest    65750d044ac8   3 months ago   117MB
busybox         latest    ff7a7936e930   7 months ago   4.28MB
centos          7         eeb6ee3f44bd   3 years ago    204MB



[root@docker ~]# mkdir demo
[root@docker ~]# cd demo/
[root@docker demo]# ls
[root@docker demo]# vim Dockerfile
[root@docker demo]# ls
Dockerfile
[root@docker demo]# cat Dockerfile 
FROM centos:7

MAINTAINER jock 1062080730@qq.com

RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup

RUN curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo && \
    curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo

RUN yum install -y wget make gcc gcc-c++ pcre-devel zlib-devel openssl-devel && \
    yum clean all

RUN wget https://nginx.org/download/nginx-1.24.0.tar.gz && \
    tar -zxf nginx-1.24.0.tar.gz && \
    rm nginx-1.24.0.tar.gz

WORKDIR /nginx-1.24.0
RUN ./configure --prefix=/usr/local/nginx --with-http_ssl_module && \
    make && \
    make install

RUN mkdir -p /usr/local/nginx/html && \
    echo "hello jock | welcome to nginx! | version 1.0" > /usr/local/nginx/html/index.html

RUN echo '#!/bin/bash' > /start.sh && \
    echo 'hostname > /usr/local/nginx/html/hostname.html' >> /start.sh && \
    echo '/usr/local/nginx/sbin/nginx -g "daemon off;"' >> /start.sh && \
    chmod +x /start.sh

EXPOSE 80

CMD ["/start.sh"]



[root@docker demo]# docker build -t myapp1.0 .
[+] Building 147.5s (13/13) FINISHED                                       docker:default
 => [internal] load build definition from Dockerfile                                 0.0s
 => => transferring dockerfile: 1.14kB                                               0.0s
 => WARN: MaintainerDeprecated: Maintainer instruction is deprecated in favor of us  0.0s
 => [internal] load metadata for docker.io/library/centos:7                          0.0s
 => [internal] load .dockerignore                                                    0.0s
 => => transferring context: 2B                                                      0.0s
 => [1/9] FROM docker.io/library/centos:7                                            0.0s
 => [2/9] RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.rep  0.2s
 => [3/9] RUN curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/r  0.4s
 => [4/9] RUN yum install -y wget make gcc gcc-c++ pcre-devel zlib-devel openssl-  123.0s
 => [5/9] RUN wget https://nginx.org/download/nginx-1.24.0.tar.gz &&     tar -zxf n  2.4s 
 => [6/9] WORKDIR /nginx-1.24.0                                                      0.0s 
 => [7/9] RUN ./configure --prefix=/usr/local/nginx --with-http_ssl_module &&       20.7s 
 => [8/9] RUN mkdir -p /usr/local/nginx/html &&     echo "hello jock | welcome to n  0.1s 
 => [9/9] RUN echo '#!/bin/bash' > /start.sh &&     echo 'hostname > /usr/local/ngi  0.1s 
 => exporting to image                                                               0.5s 
 => => exporting layers                                                              0.5s 
 => => writing image sha256:9fc60c39bb353c478d9ddffd9599c5d5be1b3667a199a7b849aa0bf  0.0s 
 => => naming to docker.io/library/myapp1.0                                          0.0s 

 1 warning found (use docker --debug to expand):
 - MaintainerDeprecated: Maintainer instruction is deprecated in favor of using label (line 3)

[root@docker demo]# docker run --name myapp -d -p 80:80 myapp1.0:latest 
ded4100304c454ca8871f9db9384eb2736e6382cfad289e270262e43d85616e9
[root@docker demo]# docker ps
CONTAINER ID   IMAGE             COMMAND       CREATED          STATUS          PORTS                                 NAMES
ded4100304c4   myapp1.0:latest   "/start.sh"   11 seconds ago   Up 10 seconds   0.0.0.0:80->80/tcp, [::]:80->80/tcp   myapp
[root@docker demo]# curl localhost
hello jock | welcome to nginx! | version 1.0
[root@docker demo]# curl localhost/hostname.html
ded4100304c4
  • nginx
[root@docker demo]# rm -rf *
[root@docker demo]# vim Dockerfile
[root@docker demo]# cat Dockerfile
FROM centos:7

MAINTAINER jock 1062080730@qq.com

RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup

RUN curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo && \
    curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo

RUN yum -y install epel-release && \
    yum -y install nginx && \
    yum clean all

COPY error.html /usr/share/nginx/html/error.html

RUN echo 'server { error_page 404 /error.html; location = /error.html { root /usr/share/nginx/html; } }' > /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
[root@docker demo]# vim error.html
[root@docker demo]# cat error.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>自定义错误页面</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            padding: 50px;
        }
        h1 {
            color: #333;
        }
        p {
            color: #666;
        }
    </style>
</head>
<body>
    <h1>哎呀!页面未找到</h1>
    <p>你所请求的页面可能已经被移除或者地址错误。</p>
</body>
</html>
[root@docker demo]# ls
Dockerfile  error.html
[root@docker demo]# docker build -t error:1.0 .
[+] Building 34.6s (11/11) FINISHED                                        docker:default
 => [internal] load build definition from Dockerfile                                 0.0s
 => => transferring dockerfile: 732B                                                 0.0s
 => WARN: MaintainerDeprecated: Maintainer instruction is deprecated in favor of us  0.0s
 => [internal] load metadata for docker.io/library/centos:7                          0.0s
 => [internal] load .dockerignore                                                    0.0s
 => => transferring context: 2B                                                      0.0s
 => [1/6] FROM docker.io/library/centos:7                                            0.0s
 => [internal] load build context                                                    0.0s
 => => transferring context: 674B                                                    0.0s
 => CACHED [2/6] RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-B  0.0s
 => CACHED [3/6] RUN curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyu  0.0s
 => [4/6] RUN yum -y install epel-release &&     yum -y install nginx &&     yum c  34.3s
 => [5/6] COPY error.html /usr/share/nginx/html/error.html                           0.0s
 => [6/6] RUN echo 'server { error_page 404 /error.html; location = /error.html { r  0.2s
 => exporting to image                                                               0.2s 
 => => exporting layers                                                              0.2s 
 => => writing image sha256:f658b54fa24cce7c0322ff844402e5917495b98650df3e43e78ff33  0.0s 
 => => naming to docker.io/library/error:1.0                                         0.0s 

 1 warning found (use docker --debug to expand):
 - MaintainerDeprecated: Maintainer instruction is deprecated in favor of using label (line 3)

[root@docker demo]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
error        1.0       f658b54fa24c   28 seconds ago   262MB
myapp        1.0       c48517613d32   7 minutes ago    210MB
myapp1.0     latest    9fc60c39bb35   15 minutes ago   368MB
kibana       8.17.3    7dcda0c152aa   7 weeks ago      1.18GB
mysql        8.4.4     4a8a163431d3   3 months ago     769MB
centos       7         eeb6ee3f44bd   3 years ago      204MB
[root@docker demo]# docker run --name error -d -p 80:80 error:1.0 
aeb56da0b8e066c9b135937f8e10ac934ba5e3fe3f87cd126b75e8a2e4409c04
[root@docker demo]# docker ps
CONTAINER ID   IMAGE       COMMAND                  CREATED         STATUS         PORTS                                 NAMES
aeb56da0b8e0   error:1.0   "nginx -g 'daemon of…"   4 seconds ago   Up 3 seconds   0.0.0.0:80->80/tcp, [::]:80->80/tcp   error
[root@docker demo]# curl localhost/error.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>自定义错误页面</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            padding: 50px;
        }
        h1 {
            color: #333;
        }
        p {
            color: #666;
        }
    </style>
</head>
<body>
    <h1>哎呀!页面未找到</h1>
    <p>你所请求的页面可能已经被移除或者地址错误。</p>
</body>
</html>
  • go
[root@docker demo]# vim main.go
[root@docker demo]# cat main.go 
package main

import (
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello, HTTPS!"))
	})

	log.Println("Server running on https://localhost:443")
	err := http.ListenAndServeTLS(":443", "server.crt", "server.key", nil)
	if err != nil {
		log.Fatal("ListenAndServeTLS: ", err)
	}
}

[root@docker demo]# openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
    -keyout server.key -out server.crt \
    -subj "/C=CN/ST=Demo/L=Demo/O=Demo/CN=localhost"
.............+++++++++++++++++++++++++++++++++++++++*..+...+..+............+.+........+....+..+++++++++++++++++++++++++++++++++++++++*.+.......+.....+......+....+..............+.+.........+...........+.+..+............+.+..+......+.......+...+.........+..+.+.....+....+...+........+...+.......+........+......+.+...............+.....+....+......+............+..+.........+....+.....................+............+..+.+..+....+........+............+.+.................+..........+..+.+.....+...+......+.............+......+......+...+...+..+......+....+..+.+.........+.....+.............+..+.+............+..+....+..+...+.........+.+..+....+.....+.............+......+......++++++
....+....+...+...........+....+...+..+++++++++++++++++++++++++++++++++++++++*.........+....+......+.................+...+.+...+......+..+++++++++++++++++++++++++++++++++++++++*.+.+.........+......+......+.........+.....+...+...+...+.+........+..........+..+.........+.........+.......+.....+..........+...+......+..+...+.+...+...+..+.+...........+.+........+......+................+............+..+.............+.....+...+...+.....................+..........+..................+...+..................+..+.......+..+....+.....................+...+...+..............+.+...............+..+......+.........+......+.+.....+......+...+....+..+...+...+............+.+......+......+...+...........+.+...+............+..+...+...+......+.+..+............+.+..............+....+..+....+..............+.+...+..........................++++++
-----

[root@docker demo]# vim Dockerfile
[root@docker demo]# cat Dockerfile
FROM golang:alpine

MAINTAINER jock 1062080730@qq.com

WORKDIR /app

COPY main.go .

RUN go build -o main main.go

COPY server.crt .
COPY server.key .

EXPOSE 443

CMD ["./main"]





### 回答1: Docker是一种容器化技术,用于隔离应用程序和它们的依赖,并以轻量级和可移植的方式进行分发和部署。而Docker Compose和DockerfileDocker生态系统中常用的工具,用于管理和构建多个容器化应用程序。 Docker Compose是一个命令行工具,通过一个简单的文本文件(通常是YAML格式)来定义、配置和运行多个Docker容器。它提供了一种非常简便的方式来描述和管理各个容器之间的关联性,同时还能够一次性启动、停止和删除整个应用程序的所有容器。通过Docker Compose,我们可以轻松地创建和管理包含多个服务的复杂应用程序,例如前端应用程序和后端数据库。 Dockerfile是一个用于定义Docker镜像的文本文件,其中包含了构建镜像所需的指令和配置。通过编写Dockerfile,我们可以描述应用程序的运行环境、依赖关系和启动命令等信息。Docker通过读取Dockerfile并按照其中的指令逐步构建镜像,最终生成一个可执行的容器化应用程序。对于一个基于Nginx的Web应用程序,我们可以使用Dockerfile来定义Nginx的配置和静态资源,然后通过构建镜像来打包应用程序。 综上所述,docker-compose、Dockerfile和Nginx可以结合使用,实现多个容器之间的协同工作和复杂应用程序的构建。我们可以使用Docker Compose来管理包含Nginx容器的应用程序,通过Dockerfile来定义Nginx镜像的构建过程,最终实现一个可部署的Nginx容器化应用程序。这种方式能够提高应用程序的可移植性、可复用性和易于管理性,为开发人员和运维人员带来许多便利。 ### 回答2: docker-compose是一个可以管理多个容器的工具,它使用一个YAML文件来定义容器的配置信息。dockerfile是用来构建镜像的脚本文件,它包含了一系列的命令来描述容器的构建过程。nginx是一个开源的高性能的Web服务器和反向代理服务器。 使用docker-compose可以很方便地定义和管理多个容器之间的关系和依赖。可以在docker-compose.yaml文件中定义多个服务,每个服务对应一个容器,而这些容器可以通过互联网络进行通信。例如,可以定义一个nginx服务,同时还可以定义一个后端应用程序的服务,并且将两个服务连接在一起。使用docker-compose up命令可以启动所有定义的服务,并且可以使用docker-compose logs命令来查看日志信息。 dockerfile是一个用来描述构建镜像过程的文件,它包含了一系列的命令来指定容器中的操作。在dockerfile中可以使用FROM命令指定基础镜像,然后使用RUN命令来执行一些操作,比如安装软件包、配置环境等。对于nginx这个容器,可以使用dockerfile来构建自定义的镜像,并且可以在其中配置nginx的相关设置,例如端口、路由规则等。 通过使用docker-compose和dockerfile,可以很轻松地将nginx部署到容器中,并且可以定义nginx和其他服务之间的联系和依赖关系。这样可以更加方便地进行应用程序的部署和管理,同时也可以通过容器的隔离性和易于移植性来提高应用程序的安全性和可靠性。 ### 回答3: Docker Compose和Dockerfile是两个与Docker相关的重要概念,而Nginx则是一款常用的高性能Web服务器和反向代理服务器软件。 Docker Compose是一个用于定义和运行多个Docker容器的工具。它通过一个单独的YAML配置文件,可以定义容器之间的关系和依赖,并且可以一键式地启动、停止和管理这些容器。使用Docker Compose,我们可以方便地把多个服务组合在一起,比如前端应用、后端应用和数据库等。 Dockerfile是一个用于构建Docker镜像的文本文件。它包含一系列的指令,用于告诉Docker引擎如何构建镜像。通过Dockerfile,我们可以自定义镜像的内容和配置,包括基础镜像选择、软件安装、文件复制、环境变量设置等。Dockerfile的编写可以帮助我们实现镜像的自动化构建和版本管理。 Nginx是一款轻量级的高性能Web服务器和反向代理服务器软件。它具有占用资源少、处理并发请求能力强等特点,被广泛用于构建高性能的Web应用架构。通过使用Nginx,我们可以实现负载均衡、反向代理、静态文件缓存、SSL加密等功能。 结合这三个概念,我们可以运用Docker Compose来定义和管理多个容器,比如前端应用容器和Nginx容器。通过Dockerfile,我们可以定义Nginx容器的自定义镜像,包括选择基础镜像、安装Nginx和相关依赖、设置Nginx配置文件等。然后,通过Docker Compose一键式地启动这些容器,实现前端应用和Nginx服务器的整合。 总之,Docker Compose、Dockerfile和Nginx是三个在Docker应用开发和部署中非常重要的概念和工具。它们可以帮助我们方便地管理和构建容器,实现应用的快速部署和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值