From 56d1c908a8093e843f93a2d5fc7eafda2628df31 Mon Sep 17 00:00:00 2001 From: Zhang Peng Date: Thu, 21 Feb 2019 19:26:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 140 ++-- docs/README.md | 15 + docs/docker/README.md | 29 - docs/docker/appendix/README.md | 8 - docs/docker/appendix/docker-cli.md | 139 +++- docs/docker/appendix/docker-resource.md | 30 - docs/docker/appendix/docker-resources.md | 30 + docs/docker/appendix/images/docker-cli.png | Bin 63126 -> 0 bytes docs/docker/basics/docker-container.md | 261 +++++++ docs/docker/basics/docker-containers.md | 1 - docs/docker/basics/docker-dockerfile.md | 667 ++++++++++++++-- docs/docker/basics/docker-image.md | 491 ++++++++++++ docs/docker/basics/docker-images.md | 66 -- docs/docker/basics/docker-install.md | 74 -- docs/docker/basics/docker-introduction.md | 35 - docs/docker/basics/docker-repository.md | 100 +++ docs/docker/containers-and-vm.png | Bin 47867 -> 0 bytes docs/docker/docker-cheat-sheet.md | 734 ------------------ docs/docker/docker-introduction.md | 95 +++ docs/docker/docker-quickstart.md | 133 +--- docs/docker/docker.md | 70 ++ docs/kubernetes/kubernetes.md | 176 +++++ docs/linux/README.md | 86 +- .../01.查看Linux命令帮助信息.md | 0 .../{ => cli}/02.Linux文件目录管理.md | 39 +- .../03.Linux文件内容查看编辑.md | 0 .../04.Linux文件压缩和解压.md | 0 docs/linux/{ => cli}/05.Linux用户管理.md | 0 docs/linux/{ => cli}/06.Linux系统管理.md | 0 docs/linux/{ => cli}/07.Linux网络管理.md | 0 docs/linux/{ => cli}/08.Linux硬件管理.md | 0 docs/linux/{ => cli}/09.Linux软件管理.md | 0 docs/linux/cli/README.md | 17 + docs/linux/{ => cli}/命令行的艺术.md | 2 +- .../ops/linux典型运维应用.md} | 3 +- docs/linux/{ => ops}/samba使用详解.md | 0 docs/{ => linux/ops/service}/apollo/README.md | 0 .../ops/service}/apollo/apollo.xmind | Bin .../ops/service}/elastic/README.md | 0 .../ops/service}/elastic/elastic-beats.md | 0 .../ops/service}/elastic/elastic-kibana.md | 0 .../ops/service}/elastic/elastic-logstash.md | 0 .../service}/elastic/elastic-quickstart.md | 0 docs/linux/ops/service/gitlab/gitlab-ci.md | 3 + .../ops/service/gitlab/gitlab-install.md} | 28 +- .../ops/service/gitlab/gitlab-quickstart.md | 76 ++ docs/{tool => linux/ops/service}/jdk.md | 5 +- docs/{tool => linux/ops/service}/jenkins.md | 11 +- docs/{tool => linux/ops/service}/kafka.md | 5 + docs/{tool => linux/ops/service}/nexus.md | 11 +- docs/{tool => linux/ops/service}/nodejs.md | 5 + docs/{tool => linux/ops/service}/rocketmq.md | 9 +- docs/{tool => linux/ops/service}/svn.md | 0 docs/{tool => linux/ops/service}/tomcat.md | 5 + docs/{tool => linux/ops/service}/zookeeper.md | 5 + docs/{ => linux/scripts}/python.md | 0 docs/{ => linux/scripts}/shell.md | 0 docs/{ => linux/tool}/git/README.md | 0 .../{ => linux/tool}/git/advanced/git-flow.md | 0 .../tool}/git/appendix/git-command.md | 0 docs/{ => linux/tool}/git/appendix/git-faq.md | 0 .../tool}/git/appendix/git-resource.md | 4 +- .../git/appendix/github-git-cheat-sheet.pdf | Bin .../tool}/git/basics/git-configuration.md | 0 .../tool}/git/basics/git-installation.md | 0 docs/{ => linux/tool}/git/git-quickstart.md | 0 docs/{ => linux/tool}/git/git.xmind | Bin docs/{ => linux/tool}/vim.md | 0 68 files changed, 2355 insertions(+), 1253 deletions(-) create mode 100644 docs/README.md delete mode 100644 docs/docker/README.md delete mode 100644 docs/docker/appendix/README.md delete mode 100644 docs/docker/appendix/docker-resource.md create mode 100644 docs/docker/appendix/docker-resources.md delete mode 100644 docs/docker/appendix/images/docker-cli.png create mode 100644 docs/docker/basics/docker-container.md delete mode 100644 docs/docker/basics/docker-containers.md create mode 100644 docs/docker/basics/docker-image.md delete mode 100644 docs/docker/basics/docker-images.md delete mode 100644 docs/docker/basics/docker-install.md delete mode 100644 docs/docker/basics/docker-introduction.md create mode 100644 docs/docker/basics/docker-repository.md delete mode 100644 docs/docker/containers-and-vm.png delete mode 100644 docs/docker/docker-cheat-sheet.md create mode 100644 docs/docker/docker-introduction.md create mode 100644 docs/docker/docker.md create mode 100644 docs/kubernetes/kubernetes.md rename docs/linux/{ => cli}/01.查看Linux命令帮助信息.md (100%) rename docs/linux/{ => cli}/02.Linux文件目录管理.md (92%) rename docs/linux/{ => cli}/03.Linux文件内容查看编辑.md (100%) rename docs/linux/{ => cli}/04.Linux文件压缩和解压.md (100%) rename docs/linux/{ => cli}/05.Linux用户管理.md (100%) rename docs/linux/{ => cli}/06.Linux系统管理.md (100%) rename docs/linux/{ => cli}/07.Linux网络管理.md (100%) rename docs/linux/{ => cli}/08.Linux硬件管理.md (100%) rename docs/linux/{ => cli}/09.Linux软件管理.md (100%) create mode 100644 docs/linux/cli/README.md rename docs/linux/{ => cli}/命令行的艺术.md (98%) rename docs/{deploy/linux基本配置.md => linux/ops/linux典型运维应用.md} (94%) rename docs/linux/{ => ops}/samba使用详解.md (100%) rename docs/{ => linux/ops/service}/apollo/README.md (100%) rename docs/{ => linux/ops/service}/apollo/apollo.xmind (100%) rename docs/{tool => linux/ops/service}/elastic/README.md (100%) rename docs/{tool => linux/ops/service}/elastic/elastic-beats.md (100%) rename docs/{tool => linux/ops/service}/elastic/elastic-kibana.md (100%) rename docs/{tool => linux/ops/service}/elastic/elastic-logstash.md (100%) rename docs/{tool => linux/ops/service}/elastic/elastic-quickstart.md (100%) create mode 100644 docs/linux/ops/service/gitlab/gitlab-ci.md rename docs/{tool/gitlab.md => linux/ops/service/gitlab/gitlab-install.md} (91%) create mode 100644 docs/linux/ops/service/gitlab/gitlab-quickstart.md rename docs/{tool => linux/ops/service}/jdk.md (96%) rename docs/{tool => linux/ops/service}/jenkins.md (92%) rename docs/{tool => linux/ops/service}/kafka.md (97%) rename docs/{tool => linux/ops/service}/nexus.md (95%) rename docs/{tool => linux/ops/service}/nodejs.md (90%) rename docs/{tool => linux/ops/service}/rocketmq.md (92%) rename docs/{tool => linux/ops/service}/svn.md (100%) rename docs/{tool => linux/ops/service}/tomcat.md (90%) rename docs/{tool => linux/ops/service}/zookeeper.md (94%) rename docs/{ => linux/scripts}/python.md (100%) rename docs/{ => linux/scripts}/shell.md (100%) rename docs/{ => linux/tool}/git/README.md (100%) rename docs/{ => linux/tool}/git/advanced/git-flow.md (100%) rename docs/{ => linux/tool}/git/appendix/git-command.md (100%) rename docs/{ => linux/tool}/git/appendix/git-faq.md (100%) rename docs/{ => linux/tool}/git/appendix/git-resource.md (97%) rename docs/{ => linux/tool}/git/appendix/github-git-cheat-sheet.pdf (100%) rename docs/{ => linux/tool}/git/basics/git-configuration.md (100%) rename docs/{ => linux/tool}/git/basics/git-installation.md (100%) rename docs/{ => linux/tool}/git/git-quickstart.md (100%) rename docs/{ => linux/tool}/git/git.xmind (100%) rename docs/{ => linux/tool}/vim.md (100%) diff --git a/README.md b/README.md index 9e8dbb7..4613ced 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# OS +# 操作系统指南 > 作为研发工程师,谁还没干过点运维的活?:joy: > @@ -8,90 +8,88 @@ ## :books: 内容 -### [Linux](docs/linux/README.md) +### [Linux](docs/linux) -- [查看 Linux 命令帮助信息](docs/linux/01.查看Linux命令帮助信息.md) - 关键词:`help`, `whatis`, `info`, `which`, `whereis`, `man` -- [Linux 文件目录管理](docs/linux/02.Linux文件目录管理.md) - 关键词:`cd`, `ls`, `pwd`, `mkdir`, `rmdir`, `tree`, `touch`, `ln`, `rename`, `stat`, `file`, `chmod`, `chown`, `locate`, `find`, `cp`, `mv`, `rm` -- [Linux 文件内容查看命令](docs/linux/03.Linux文件内容查看编辑.md) - 关键词:`cat`, `head`, `tail`, `more`, `less`, `sed`, `vi`, `grep` -- [Linux 文件压缩和解压](docs/linux/04.Linux文件压缩和解压.md) - 关键词:`tar`, `gzip`, `zip`, `unzip` -- [Linux 用户管理](docs/linux/05.Linux用户管理.md) - 关键词:`groupadd`, `groupdel`, `groupmod`, `useradd`, `userdel`, `usermod`, `passwd`, `su`, `sudo` -- [Linux 系统管理](docs/linux/06.Linux系统管理.md) - 关键词:`reboot`, `exit`, `shutdown`, `date`, `mount`, `umount`, `ps`, `kill`, `systemctl`, `service`, `crontab` -- [Linux 网络管理](docs/linux/07.Linux网络管理.md) - 关键词:关键词:`curl`, `wget`, `telnet`, `ip`, `hostname`, `ifconfig`, `route`, `ssh`, `ssh-keygen`, `firewalld`, `iptables`, `host`, `nslookup`, `nc`/`netcat`, `ping`, `traceroute`, `netstat` -- [Linux 硬件管理](docs/linux/08.Linux硬件管理.md) - 关键词:`df`, `du`, `top`, `free`, `iotop` -- [Linux 软件管理](docs/linux/09.Linux软件管理.md) - 关键词:`rpm`, `yum`, `apt-get` -- [samba 使用详解](docs/linux/samba使用详解.md) -- [命令行的艺术(转载)](docs/linux/命令行的艺术.md) +#### [Linux 命令](docs/linux/cli) -### [Shell](docs/shell.md) +> 根据应用场景,将常见 Linux 命令分门别类的一一介绍。 +> +> 如果想快速学习,推荐参考这篇文章:[命令行的艺术(转载)](docs/linux/cli/命令行的艺术.md) -### [Python](docs/python.md) +1. [查看 Linux 命令帮助信息](docs/linux/cli/01.查看Linux命令帮助信息.md) - 关键词:`help`, `whatis`, `info`, `which`, `whereis`, `man` +2. [Linux 文件目录管理](docs/linux/cli/02.Linux文件目录管理.md) - 关键词:`cd`, `ls`, `pwd`, `mkdir`, `rmdir`, `tree`, `touch`, `ln`, `rename`, `stat`, `file`, `chmod`, `chown`, `locate`, `find`, `cp`, `mv`, `rm` +3. [Linux 文件内容查看命令](docs/linux/cli/03.Linux文件内容查看编辑.md) - 关键词:`cat`, `head`, `tail`, `more`, `less`, `sed`, `vi`, `grep` +4. [Linux 文件压缩和解压](docs/linux/cli/04.Linux文件压缩和解压.md) - 关键词:`tar`, `gzip`, `zip`, `unzip` +5. [Linux 用户管理](docs/linux/cli/05.Linux用户管理.md) - 关键词:`groupadd`, `groupdel`, `groupmod`, `useradd`, `userdel`, `usermod`, `passwd`, `su`, `sudo` +6. [Linux 系统管理](docs/linux/cli/06.Linux系统管理.md) - 关键词:`reboot`, `exit`, `shutdown`, `date`, `mount`, `umount`, `ps`, `kill`, `systemctl`, `service`, `crontab` +7. [Linux 网络管理](docs/linux/cli/07.Linux网络管理.md) - 关键词:关键词:`curl`, `wget`, `telnet`, `ip`, `hostname`, `ifconfig`, `route`, `ssh`, `ssh-keygen`, `firewalld`, `iptables`, `host`, `nslookup`, `nc`/`netcat`, `ping`, `traceroute`, `netstat` +8. [Linux 硬件管理](docs/linux/cli/08.Linux硬件管理.md) - 关键词:`df`, `du`, `top`, `free`, `iotop` +9. [Linux 软件管理](docs/linux/cli/09.Linux软件管理.md) - 关键词:`rpm`, `yum`, `apt-get` -### [Vim](docs/vim.md) +#### [Linux 工具](docs/linux/tool) -### [Docker](docs/docker/README.md) +- [Git](docs/linux/tool/git) +- [Vim](docs/linux/tool/vim.md) -### Windows +#### [Linux 脚本编程](docs/linux/scripts) -- [Windows 工具](docs/windows/Windows工具.md) +- [Shell](docs/linux/scripts/shell.md) +- [Python](docs/linux/scripts/python.md) -## :hammer_and_pick: 常见软件安装/配置/使用指南 +#### [Linux 运维](docs/linux/ops) > :bulb: **说明** -> +> > 这里总结了多种常用研发软件的安装、配置、使用指南。并提供基本安装、运行的脚本。 > > [环境部署工具](codes/deploy/README.md) :适合开发、运维人员,在 [CentOS](https://www.centos.org/) 机器上安装常用命令工具或开发软件。 -> -> - *`Scripts`:安装配置脚本,按照说明安装使用即可。* -> - *`Docs`: 安装配置文档,说明安装的方法以及一些注意事项。* -> - *`Tutorial`: 教程文档。* +> +> - _`Scripts`:安装配置脚本,按照说明安装使用即可。_ +> - _`Docs`: 安装配置文档,说明安装的方法以及一些注意事项。_ +> - _`Tutorial`: 教程文档。_ -#### 研发环境 +- 研发环境 + - JDK + - | [**`Scripts`**](codes/deploy/tool/jdk) | [**`Docs`**](docs/linux/ops/service/jdk.md) | + - Maven + - | [**`Scripts`**](codes/deploy/tool/maven) | [**`Tutorial`**](https://github.com/dunwu/javastack/tree/master/docs/javatool/build/maven) | + - Nginx + - | [**`Scripts`**](codes/deploy/tool/nginx) | [**`Tutorial`**](https://github.com/dunwu/nginx-tutorial) | + - Nodejs + - | [**`Scripts`**](codes/deploy/tool/nodejs) | [**`Docs`**](docs/linux/ops/service/nodejs.md) | + - Tomcat + - | [**`Scripts`**](codes/deploy/tool/tomcat) | [**`Docs`**](docs/linux/ops/service/tomcat.md) | + - Zookeeper + - | [**`Scripts`**](codes/deploy/tool/zookeeper) | [**`Docs`**](docs/linux/ops/service/zookeeper.md) | +- 研发工具 + - Nexus - Maven 私服。 + - | [**`Docs`**](docs/linux/ops/service/nexus.md) | + 。 + - Jenkins - 持续集成和持续交付平台。 + - | [**`Scripts`**](codes/deploy/tool/jenkins) | [**`Docs`**](docs/linux/ops/service/jenkins.md) | + - Elastic - 常被称为 `ELK` ,是 Java 世界最流行的分布式日志解决方案 。 `ELK` 是 Elastic 公司旗下三款产品 [ElasticSearch](https://www.elastic.co/products/elasticsearch) 、[Logstash](https://www.elastic.co/products/logstash) 、[Kibana](https://www.elastic.co/products/kibana) 的首字母组合。 + - | [**`Tutorial`**](docs/linux/ops/service/elastic/README.md) | +- 版本控制 + - Gitlab - Git 代码管理平台 + - Svn - Svn 是 Subversion 的简称,是一个开放源代码的版本控制系统,它采用了分支管理系统。 + - | [**`Docs`**](docs/linux/ops/service/svn.md) | +- 消息中间件 + - Kafka - 应该是 Java 世界最流行的消息中间件了吧。 + - | [**`Scripts`**](codes/deploy/tool/kafka) | [**`Docs`**](docs/linux/ops/service/kafka.md) | + - RocketMQ - 阿里巴巴开源的消息中间件。 + - | [**`Scripts`**](codes/deploy/tool/rocketmq) | [**`Docs`**](docs/linux/ops/service/rocketmq.md) | +- 数据库 + - Mysql - 关系型数据库 + - | [**`Docs`**](https://github.com/dunwu/database/blob/master/docs/mysql/install-mysql.md) | + - PostgreSQL - 关系型数据库 + - | [**`Docs`**](https://github.com/dunwu/database/blob/master/docs/postgresql.md#安装) | + - Mongodb - Nosql + - | [**`Scripts`**](codes/deploy/tool/mongodb) | [**`Docs`**](https://github.com/dunwu/database/blob/master/docs/mongodb/install-mongodb.md) | + - Redis - Nosql + - | [**`Scripts`**](codes/deploy/tool/redis) | [**`Docs`**](https://github.com/dunwu/database/blob/master/docs/redis/install-redis.md) | -- JDK - - | [**`Scripts`**](codes/deploy/tool/jdk) | [**`Docs`**](docs/tool/jdk.md) | -- Maven - - | [**`Scripts`**](codes/deploy/tool/maven) | [**`Tutorial`**](https://github.com/dunwu/javastack/tree/master/docs/javatool/build/maven) | -- Nginx - - | [**`Scripts`**](codes/deploy/tool/nginx) | [**`Tutorial`**](https://github.com/dunwu/nginx-tutorial) | -- Nodejs - - | [**`Scripts`**](codes/deploy/tool/nodejs) | [**`Docs`**](docs/tool/nodejs.md) | -- Tomcat - - | [**`Scripts`**](codes/deploy/tool/tomcat) | [**`Docs`**](docs/tool/tomcat.md) | -- Zookeeper - - | [**`Scripts`**](codes/deploy/tool/zookeeper) | [**`Docs`**](docs/tool/zookeeper.md) | +### [Windows](docs/windows) -#### 研发工具 +- [Windows 工具](docs/windows/Windows工具.md) -- Nexus - Maven 私服。 - - | [**`Docs`**](docs/tool/nexus.md) | -- Gitlab - Git 代码管理平台。 -- Jenkins - 持续集成和持续交付平台。 - - | [**`Scripts`**](codes/deploy/tool/jenkins) | [**`Docs`**](docs/tool/jenkins.md) | -- Elastic - 常被称为 `ELK` ,是 Java 世界最流行的分布式日志解决方案 。 `ELK` 是 Elastic 公司旗下三款产品 [ElasticSearch](https://www.elastic.co/products/elasticsearch) 、[Logstash](https://www.elastic.co/products/logstash) 、[Kibana](https://www.elastic.co/products/kibana) 的首字母组合。 - - | [**`Tutorial`**](docs/tool/elastic/README.md) | - -#### 版本控制 - -- Git - - | [**`Tutorial`**](docs/git/README.md) | -- Svn - Svn 是 Subversion 的简称,是一个开放源代码的版本控制系统,它采用了分支管理系统。 - - | [**`Docs`**](docs/tool/svn.md) | - -#### 消息中间件 - -- Kafka - 应该是 Java 世界最流行的消息中间件了吧。 - - | [**`Scripts`**](codes/deploy/tool/kafka) | [**`Docs`**](docs/tool/kafka.md) | -- RocketMQ - 阿里巴巴开源的消息中间件。 - - | [**`Scripts`**](codes/deploy/tool/rocketmq) | [**`Docs`**](docs/tool/rocketmq.md) | - -#### 数据库 - -- Mysql - 关系型数据库 - - | [**`Docs`**](https://github.com/dunwu/database/blob/master/docs/mysql/install-mysql.md) | -- PostgreSQL - 关系型数据库 - - | [**`Docs`**](https://github.com/dunwu/database/blob/master/docs/postgresql.md#安装) | -- Mongodb - Nosql - - | [**`Scripts`**](codes/deploy/tool/mongodb) | [**`Docs`**](https://github.com/dunwu/database/blob/master/docs/mongodb/install-mongodb.md) | -- Redis - Nosql - - | [**`Scripts`**](codes/deploy/tool/redis) | [**`Docs`**](https://github.com/dunwu/database/blob/master/docs/redis/install-redis.md) | +### [Docker](docs/docker) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..88a2d0a --- /dev/null +++ b/docs/README.md @@ -0,0 +1,15 @@ +# 操作系统 + +> :dart: 所有配套源码整理归档在 [**os-tutorial**](https://github.com/dunwu/os-tutorial) 项目中。 + +## :memo: 知识点 + +- [Linux](linux) +- [Windows](windows) +- [Docker](docker) + +## :books: 学习资源 + +## :door: 传送门 + +| [回首頁](https://github.com/dunwu/os-tutorial) | diff --git a/docs/docker/README.md b/docs/docker/README.md deleted file mode 100644 index 01c9083..0000000 --- a/docs/docker/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# Docker - -> [Docker](https://www.docker.com/) 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。 -> -> 容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app)。几乎没有性能开销,可以很容易地在机器和数据中心中运行。最重要的是,他们不依赖于任何语言、框架或包装系统。 - -## 知识大纲 - -- [x] [Docker 快速指南](docker-quickstart.md) -- [x] [Docker Cheat Sheet](docker-cheat-sheet.md) -- [ ] 基础篇(basics) - - [x] [Docker 简介](basics/docker-introduction.md) - - [x] [Docker 安装](basics/docker-install.md) - - [x] [Docker 之 Hello World](basics/docker-helloworld.md) - - [ ] 配置(configuration) -- [ ] 进阶篇(advanced) - - [x] [Docker 的设计](advanced/docker-design.md) -- [x] 实践篇(practice) - - [x] [Docker 安装 MySQL](practice/docker-install-mysql.md) - - [x] [Docker 安装 Nginx](practice/docker-install-nginx.md) -- [ ] 常见问题(faq) -- [x] [附录](appendix/README.md) - - - [x] [Docker 命令](appendix/docker-cli.md) - - [x] [Docker 资源](appendix/docker-resource.md) - - [x] [Docker 术语](appendix/docker-glossary.md) - - [x] [Docker 技巧](appendix/docker-recipe.md) - -_知识点仍在完善中。。。_ diff --git a/docs/docker/appendix/README.md b/docs/docker/appendix/README.md deleted file mode 100644 index f2a3cb3..0000000 --- a/docs/docker/appendix/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# 附录 - -## 内容 - -* [Docker 命令](docker-cli.md) -* [Docker 资源](docker-resource.md) -* [Docker 术语](docker-glossary.md) -* [Docker 技巧](docker-recipe.md) diff --git a/docs/docker/appendix/docker-cli.md b/docs/docker/appendix/docker-cli.md index 296277d..2eab462 100644 --- a/docs/docker/appendix/docker-cli.md +++ b/docs/docker/appendix/docker-cli.md @@ -1,10 +1,139 @@ # Docker 命令 -## 命令行大纲 + -![docker-cli.png](images/docker-cli.png) +- [镜像(Images)](#镜像images) +- [容器(Container)](#容器container) + - [生命周期](#生命周期) + - [启动和停止](#启动和停止) + - [信息](#信息) + - [导入 / 导出](#导入--导出) + - [执行命令](#执行命令) +- [网络(Networks)](#网络networks) + - [生命周期](#生命周期-1) + - [信息](#信息-1) + - [链接](#链接) +- [仓管中心和仓库(Registry & Repository)](#仓管中心和仓库registry--repository) +- [Dockerfile](#dockerfile) +- [卷标(Volumes)](#卷标volumes) + - [生命周期](#生命周期-2) + - [信息](#信息-2) +- [引用和引申](#引用和引申) -## 资料 + -* https://docs.docker.com/engine/reference/commandline/cli/ -* http://www.runoob.com/docker/docker-command-manual.html +## 镜像(Images) + +- [`docker image ls`](https://github.com/yeasy/docker_practice/blob/master/image/list.md) - 查看所有镜像。 +- [`docker image rm`](https://github.com/yeasy/docker_practice/blob/master/image/rm.md) - 删除本地镜像。 +- `docker import` - 从压缩文件中创建镜像。 +- `docker export` - 导出既有容器。 +- `docker build` - 从 Dockerfile 创建镜像。 +- `docker commit` - 为容器创建镜像,如果容器正在运行则会临时暂停。 +- `docker rmi` - 删除镜像。 +- `docker load` - 通过 STDIN 从压缩包加载镜像,包括镜像和标签(images and tags) (0.7 起). +- `docker save` - 通过 STDOUT 保存镜像到压缩包,包括所有的父层,标签和版本(parent layers, tags & versions) (0.7 起). +- `docker history` - 查看镜像历史记录。 +- `docker tag` - 给镜像命名打标(tags) (本地或者仓库)。 + +## 容器(Container) + +### 生命周期 + +- `docker create` - 创建一个容器但是不启动。 +- `docker rename` - 允许重命名容器。 +- `docker run` - 在同一个操作中创建并启动一个容器。 +- `docker rm` - 删除容器。 +- `docker update` - 更新容器的资源限制。 + +### 启动和停止 + +- `docker start` - 启动容器。 +- `docker stop` - 停止运行中的容器。 +- `docker restart` - 停止之后再启动容器。 +- `docker pause` - 暂停运行中的容器,将其 "冻结" 在当前状态。 +- `docker unpause` - 结束容器暂停状态。 +- `docker wait` - 阻塞,到运行中的容器停止为止。 +- `docker kill` - 向运行中容器发送 SIGKILL 指令。 +- `docker attach` - 链接到运行中容器。 + +### 信息 + +- `docker ps` - 查看运行中的所有容器。 +- `docker logs` - 从容器中获取日志。(你也可以使用自定义日志驱动,不过在 1.10 中,它只支持 json-file 和 journald) +- `docker inspect` - 查看某个容器的所有信息(包括 IP 地址)。 +- `docker events` - 从容器中获取事件(events)。 +- `docker port` - 查看容器的公开端口。 +- `docker top` - 查看容器中活动进程。 +- `docker stats` - 查看容器的资源使用情况统计信息。 +- `docker diff` - 查看容器的 FS 中有变化文件信息。 + +### 导入 / 导出 + +docker cp 在容器和本地文件系统之间复制文件或文件夹。 +docker export 将容器的文件系统切换为压缩包(tarball archive stream)输出到 STDOUT。 + +### 执行命令 + +docker exec 在容器中执行命令。 + +## 网络(Networks) + +### 生命周期 + +- `docker network create` +- `docker network rm` + +### 信息 + +- `docker network ls` +- `docker network inspect` + +### 链接 + +- `docker network connect` +- `docker network disconnect` + +## 仓管中心和仓库(Registry & Repository) + +- `docker login` - 登入仓管中心。 +- `docker logout` - 登出仓管中心。 +- `docker search` - 从仓管中心检索镜像。 +- `docker pull` - 从仓管中心拉去镜像到本地。 +- `docker push` - 从本地推送镜像到仓管中心。 + +## Dockerfile + +- .dockerignore +- FROM 为其他指令设置基础镜像(Base Image)。 +- MAINTAINER 为生成的镜像设置作者字段。 +- RUN 在当前镜像的基础上生成一个新层并执行命令。 +- CMD 设置容器默认执行命令。 +- EXPOSE 告知 Docker 容器在运行时所要监听的网络端口。注意:并没有实际上将端口设置为可访问。 +- ENV 设置环境变量。 +- ADD 将文件,文件夹或者远程文件复制到容器中。缓存无效。尽量用 COPY 代替 ADD。 +- COPY 将文件或文件夹复制到容器中。 +- ENTRYPOINT 将一个容器设置为可执行。 +- VOLUME 为外部挂载卷标或其他容器设置挂载点(mount point)。 +- USER 设置执行 RUN / CMD / ENTRYPOINT 命令的用户名。 +- WORKDIR 设置工作目录。 +- ARG 定义编译时(build-time)变量。 +- ONBUILD 添加触发指令,当该镜像被作为其他镜像的基础镜像时该指令会被触发。 +- STOPSIGNAL 设置通过系统向容器发出退出指令。 +- LABEL 将键值对元数据(key/value metadata)应用到你的镜像,容器,或者守护进程。 + +## 卷标(Volumes) + +### 生命周期 + +- `docker volume create` +- `docker volume rm` + +### 信息 + +- `docker volume ls` +- `docker volume inspect` + +## 引用和引申 + +https://github.com/wsargent/docker-cheat-sheet/tree/master/zh-cn diff --git a/docs/docker/appendix/docker-resource.md b/docs/docker/appendix/docker-resource.md deleted file mode 100644 index ef33c28..0000000 --- a/docs/docker/appendix/docker-resource.md +++ /dev/null @@ -1,30 +0,0 @@ -# Docker 资源 - -## Docker 官方资源 - -* Docker 官网:http://www.docker.com -* Docker Github:https://github.com/moby/moby -* Docker 官方文档:https://docs.docker.com/ -* Docker windows 入门:https://docs.docker.com/windows/ -* Docker Linux 入门:https://docs.docker.com/linux/ -* Docker mac 入门:https://docs.docker.com/mac/ -* Docker 用户指引:https://docs.docker.com/engine/userguide/ -* Docker 官方博客:http://blog.docker.com/ -* Docker Hub: https://hub.docker.com/ -* Docker 开源: https://www.docker.com/open-source - -## 资源整理 - -* Awesome Docker: https://github.com/veggiemonk/awesome-docker - -## Docker 中文资源 - -* Docker中文网站:https://www.docker-cn.com/ -* Docker安装手册:https://docs.docker-cn.com/engine/installation/ - -## Docker 国内镜像 - -* 网易加速器:http://hub-mirror.c.163.com -* 官方中国加速器:https://registry.docker-cn.com -* ustc的镜像:https://docker.mirrors.ustc.edu.cn -* daocloud:https://www.daocloud.io/mirror#accelerator-doc(注册后使用) diff --git a/docs/docker/appendix/docker-resources.md b/docs/docker/appendix/docker-resources.md new file mode 100644 index 0000000..b63122d --- /dev/null +++ b/docs/docker/appendix/docker-resources.md @@ -0,0 +1,30 @@ +# Docker 资源 + +## Docker 官方资源 + +- Docker 官网:http://www.docker.com +- Docker Github:https://github.com/moby/moby +- Docker 官方文档:https://docs.docker.com/ +- Docker windows 入门:https://docs.docker.com/windows/ +- Docker Linux 入门:https://docs.docker.com/linux/ +- Docker mac 入门:https://docs.docker.com/mac/ +- Docker 用户指引:https://docs.docker.com/engine/userguide/ +- Docker 官方博客:http://blog.docker.com/ +- Docker Hub: https://hub.docker.com/ +- Docker 开源: https://www.docker.com/open-source + +## 资源整理 + +- Awesome Docker: https://github.com/veggiemonk/awesome-docker + +## Docker 中文资源 + +- Docker 中文网站:https://www.docker-cn.com/ +- Docker 安装手册:https://docs.docker-cn.com/engine/installation/ + +## Docker 国内镜像 + +- 网易加速器:http://hub-mirror.c.163.com +- 官方中国加速器:https://registry.docker-cn.com +- ustc 的镜像:https://docker.mirrors.ustc.edu.cn +- daocloud:https://www.daocloud.io/mirror#accelerator-doc(注册后使用) diff --git a/docs/docker/appendix/images/docker-cli.png b/docs/docker/appendix/images/docker-cli.png deleted file mode 100644 index 16ac18380eb30425ae8a06c2c8c8e05593ca24f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63126 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 ZAeJ4lu$v33@ty>)@RKbHS!v_<^B)e)eRco< diff --git a/docs/docker/basics/docker-container.md b/docs/docker/basics/docker-container.md new file mode 100644 index 0000000..c993a7b --- /dev/null +++ b/docs/docker/basics/docker-container.md @@ -0,0 +1,261 @@ +# Docker 容器 + +容器是独立运行的一个或一组应用,以及它们的运行态环境。对应的,虚拟机可以理解为模拟运行的一整套操作系统(提供了运行态环境和其他系统环境)和跑在上面的应用。 + + + +- [启动容器](#启动容器) + - [新建并启动](#新建并启动) + - [启动已终止容器](#启动已终止容器) +- [后台运行](#后台运行) +- [终止容器](#终止容器) +- [进入容器](#进入容器) + - [`attach` 命令](#attach-命令) + - [`exec` 命令](#exec-命令) +- [导出和导入容器](#导出和导入容器) + - [导出容器](#导出容器) + - [导入容器快照](#导入容器快照) +- [删除容器](#删除容器) +- [清理所有处于终止状态的容器](#清理所有处于终止状态的容器) + + + +## 启动容器 + +启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(`stopped`)的容器重新启动。 + +因为 Docker 的容器实在太轻量级了,很多时候用户都是随时删除和新创建容器。 + +### 新建并启动 + +所需要的命令主要为 `docker run`。 + +例如,下面的命令输出一个 “Hello World”,之后终止容器。 + +```bash +$ docker run ubuntu:14.04 /bin/echo 'Hello world' +Hello world +``` + +这跟在本地直接执行 `/bin/echo 'hello world'` 几乎感觉不出任何区别。 + +下面的命令则启动一个 bash 终端,允许用户进行交互。 + +```bash +$ docker run -t -i ubuntu:14.04 /bin/bash +root@af8bae53bdd3:/# +``` + +其中,`-t` 选项让 Docker 分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上, `-i` 则让容器的标准输入保持打开。 + +在交互模式下,用户可以通过所创建的终端来输入命令,例如 + +```bash +root@af8bae53bdd3:/# pwd +/ +root@af8bae53bdd3:/# ls +bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var +``` + +当利用 `docker run` 来创建容器时,Docker 在后台运行的标准操作包括: + +- 检查本地是否存在指定的镜像,不存在就从公有仓库下载 +- 利用镜像创建并启动一个容器 +- 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层 +- 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去 +- 从地址池配置一个 ip 地址给容器 +- 执行用户指定的应用程序 +- 执行完毕后容器被终止 + +### 启动已终止容器 + +可以利用 `docker container start` 命令,直接将一个已经终止的容器启动运行。 + +容器的核心为所执行的应用程序,所需要的资源都是应用程序运行所必需的。除此之外,并没有其它的资源。可以在伪终端中利用 `ps` 或 `top` 来查看进程信息。 + +```bash +root@ba267838cc1b:/# ps + PID TTY TIME CMD + 1 ? 00:00:00 bash + 11 ? 00:00:00 ps +``` + +可见,容器中仅运行了指定的 bash 应用。这种特点使得 Docker 对资源的利用率极高,是货真价实的轻量级虚拟化。 + +## 后台运行 + +更多的时候,需要让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加 `-d` 参数来实现。 + +下面举两个例子来说明一下。 + +如果不使用 `-d` 参数运行容器。 + +```bash +$ docker run ubuntu:18.04 /bin/sh -c "while true; do echo hello world; sleep 1; done" +hello world +hello world +hello world +hello world +``` + +容器会把输出的结果 (STDOUT) 打印到宿主机上面 + +如果使用了 `-d` 参数运行容器。 + +```bash +$ docker run -d ubuntu:18.04 /bin/sh -c "while true; do echo hello world; sleep 1; done" +77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a +``` + +此时容器会在后台运行并不会把输出的结果 (STDOUT) 打印到宿主机上面(输出结果可以用 `docker logs` 查看)。 + +**注:** 容器是否会长久运行,是和 `docker run` 指定的命令有关,和 `-d` 参数无关。 + +使用 `-d` 参数启动后会返回一个唯一的 id,也可以通过 `docker container ls` 命令来查看容器信息。 + +``` +$ docker container ls +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +77b2dc01fe0f ubuntu:18.04 /bin/sh -c 'while tr 2 minutes ago Up 1 minute agitated_wright +``` + +要获取容器的输出信息,可以通过 `docker container logs` 命令。 + +```bash +$ docker container logs [container ID or NAMES] +hello world +hello world +hello world +. . . +``` + +## 终止容器 + +可以使用 `docker container stop` 来终止一个运行中的容器。 + +此外,当 Docker 容器中指定的应用终结时,容器也自动终止。 + +例如对于上一章节中只启动了一个终端的容器,用户通过 `exit` 命令或 `Ctrl+d` 来退出终端时,所创建的容器立刻终止。 + +终止状态的容器可以用 `docker container ls -a` 命令看到。例如 + +```bash +docker container ls -a +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +ba267838cc1b ubuntu:14.04 "/bin/bash" 30 minutes ago Exited (0) About a minute ago trusting_newton +98e5efa7d997 training/webapp:latest "python app.py" About an hour ago Exited (0) 34 minutes ago backstabbing_pike +``` + +处于终止状态的容器,可以通过 `docker container start` 命令来重新启动。 + +此外,`docker container restart` 命令会将一个运行态的容器终止,然后再重新启动它。 + +## 进入容器 + +在使用 `-d` 参数时,容器启动后会进入后台。 + +某些时候需要进入容器进行操作,包括使用 `docker attach` 命令或 `docker exec` 命令,推荐大家使用 `docker exec` 命令,原因会在下面说明。 + +### `attach` 命令 + +`docker attach` 是 Docker 自带的命令。下面示例如何使用该命令。 + +```bash +$ docker run -dit ubuntu +243c32535da7d142fb0e6df616a3c3ada0b8ab417937c853a9e1c251f499f550 + +$ docker container ls +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +243c32535da7 ubuntu:latest "/bin/bash" 18 seconds ago Up 17 seconds nostalgic_hypatia + +$ docker attach 243c +root@243c32535da7:/# +``` + +_注意:_ 如果从这个 stdin 中 exit,会导致容器的停止。 + +### `exec` 命令 + +#### -i -t 参数 + +`docker exec` 后边可以跟多个参数,这里主要说明 `-i` `-t` 参数。 + +只用 `-i` 参数时,由于没有分配伪终端,界面没有我们熟悉的 Linux 命令提示符,但命令执行结果仍然可以返回。 + +当 `-i` `-t` 参数一起使用时,则可以看到我们熟悉的 Linux 命令提示符。 + +```bash +$ docker run -dit ubuntu +69d137adef7a8a689cbcb059e94da5489d3cddd240ff675c640c8d96e84fe1f6 + +$ docker container ls +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +69d137adef7a ubuntu:latest "/bin/bash" 18 seconds ago Up 17 seconds zealous_swirles + +$ docker exec -i 69d1 bash +ls +bin +boot +dev +... + +$ docker exec -it 69d1 bash +root@69d137adef7a:/# +``` + +如果从这个 stdin 中 exit,不会导致容器的停止。这就是为什么推荐大家使用 `docker exec` 的原因。 + +更多参数说明请使用 `docker exec --help` 查看。 + +## 导出和导入容器 + +### 导出容器 + +如果要导出本地某个容器,可以使用 `docker export` 命令。 + +```bash +$ docker container ls -a +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +7691a814370e ubuntu:14.04 "/bin/bash" 36 hours ago Exited (0) 21 hours ago test +$ docker export 7691a814370e > ubuntu.tar +``` + +这样将导出容器快照到本地文件。 + +### 导入容器快照 + +可以使用 `docker import` 从容器快照文件中再导入为镜像,例如 + +```bash +$ cat ubuntu.tar | docker import - test/ubuntu:v1.0 +$ docker image ls +REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE +test/ubuntu v1.0 9d37a6082e97 About a minute ago 171.3 MB +``` + +此外,也可以通过指定 URL 或者某个目录来导入,例如 + +```bash +$ docker import http://example.com/exampleimage.tgz example/imagerepo +``` + +_注:用户既可以使用 docker load 来导入镜像存储文件到本地镜像库,也可以使用 docker import 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。_ + +## 删除容器 + +可以使用 `docker container rm` 来删除一个处于终止状态的容器。例如 + +```bash +$ docker container rm trusting_newton +trusting_newton +``` + +如果要删除一个运行中的容器,可以添加 `-f` 参数。Docker 会发送 `SIGKILL` 信号给容器。 + +## 清理所有处于终止状态的容器 + +用 `docker container ls -a` 命令可以查看所有已经创建的包括终止状态的容器,如果数量太多要一个个删除可能会很麻烦,用下面的命令可以清理掉所有处于终止状态的容器。 + +```bash +$ docker container prune +``` diff --git a/docs/docker/basics/docker-containers.md b/docs/docker/basics/docker-containers.md deleted file mode 100644 index 9cb77ea..0000000 --- a/docs/docker/basics/docker-containers.md +++ /dev/null @@ -1 +0,0 @@ -Docker 容器 \ No newline at end of file diff --git a/docs/docker/basics/docker-dockerfile.md b/docs/docker/basics/docker-dockerfile.md index 49ed570..4c22154 100644 --- a/docs/docker/basics/docker-dockerfile.md +++ b/docs/docker/basics/docker-dockerfile.md @@ -1,86 +1,649 @@ # Dockerfile -## 格式 + -Dockerfile 中,对于指令不区分大小写,但是推荐使用大写,来区别于参数。 +- [Dockerfile 指令](#dockerfile-指令) + - [FROM(指定基础镜像)](#from指定基础镜像) + - [RUN(执行命令)](#run执行命令) + - [COPY(复制文件)](#copy复制文件) + - [ADD(更高级的复制文件)](#add更高级的复制文件) + - [CMD(容器启动命令)](#cmd容器启动命令) + - [ENTRYPOINT(入口点)](#entrypoint入口点) + - [ENV(设置环境变量)](#env设置环境变量) + - [ARG(构建参数)](#arg构建参数) + - [VOLUME(定义匿名卷)](#volume定义匿名卷) + - [EXPOSE(暴露端口)](#expose暴露端口) + - [WORKDIR(指定工作目录)](#workdir指定工作目录) + - [USER(指定当前用户)](#user指定当前用户) + - [HEALTHCHECK(健康检查)](#healthcheck健康检查) + - [ONBUILD(为他人作嫁衣裳)](#onbuild为他人作嫁衣裳) +- [引用和引申](#引用和引申) -Dockerfile 中,将按照指令(instruction)出现次序依次执行。**注意:Dockerfile 必须从 `FROM` 指令开始。** + -Dockerfile 中,将 `#` 开头的行作为注释,除非该行是有效的解析指令(parser directives)。一行中的 `#` 标记被视为参数。注释中不支持换行符。 +## Dockerfile 指令 + +### FROM(指定基础镜像) + +> 作用: +> +> `FROM` 指令用于指定基础镜像。 + +所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个 `nginx` 镜像的容器,再进行修改一样,基础镜像是必须指定的。而 `FROM` 就是指定**基础镜像**,因此一个 `Dockerfile` 中 `FROM` 是必备的指令,并且必须是第一条指令。 + +在 [Docker Store](https://store.docker.com/) 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如 [`nginx`](https://store.docker.com/images/nginx/)、[`redis`](https://store.docker.com/images/redis/)、[`mongo`](https://store.docker.com/images/mongo/)、[`mysql`](https://store.docker.com/images/mysql/)、[`httpd`](https://store.docker.com/images/httpd/)、[`php`](https://store.docker.com/images/php/)、[`tomcat`](https://store.docker.com/images/tomcat/) 等;也有一些方便开发、构建、运行各种语言应用的镜像,如 [`node`](https://store.docker.com/images/node)、[`openjdk`](https://store.docker.com/images/openjdk/)、[`python`](https://store.docker.com/images/python/)、[`ruby`](https://store.docker.com/images/ruby/)、[`golang`](https://store.docker.com/images/golang/) 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。 + +如果没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操作系统镜像,如 [`ubuntu`](https://store.docker.com/images/ubuntu/)、[`debian`](https://store.docker.com/images/debian/)、[`centos`](https://store.docker.com/images/centos/)、[`fedora`](https://store.docker.com/images/fedora/)、[`alpine`](https://store.docker.com/images/alpine/) 等,这些操作系统的软件库为我们提供了更广阔的扩展空间。 + +除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 `scratch`。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。 + +```dockerfile +FROM scratch +... +``` + +如果你以 `scratch` 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。 + +不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,比如 [`swarm`](https://hub.docker.com/_/swarm/)、[`coreos/etcd`](https://quay.io/repository/coreos/etcd)。对于 Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接 `FROM scratch` 会让镜像体积更加小巧。使用 [Go 语言](https://golang.org/) 开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为 Go 是特别适合容器微服务架构的语言的原因之一。 + +### RUN(执行命令) + +`RUN` 指令是用来执行命令行命令的。由于命令行的强大能力,`RUN` 指令在定制镜像时是最常用的指令之一。其格式有两种: + +- _shell_ 格式:`RUN <命令>`,就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 `RUN` 指令就是这种格式。 + +```dockerfile +RUN echo '

Hello, Docker!

' > /usr/share/nginx/html/index.html +``` + +- _exec_ 格式:`RUN ["可执行文件", "参数1", "参数2"]`,这更像是函数调用中的格式。 + +既然 `RUN` 就像 Shell 脚本一样可以执行命令,那么我们是否就可以像 Shell 脚本一样把每个命令对应一个 RUN 呢?比如这样: + +```dockerfile +FROM debian:jessie + +RUN apt-get update +RUN apt-get install -y gcc libc6-dev make +RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" +RUN mkdir -p /usr/src/redis +RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 +RUN make -C /usr/src/redis +RUN make -C /usr/src/redis install +``` + +之前说过,Dockerfile 中每一个指令都会建立一层,`RUN` 也不例外。每一个 `RUN` 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,`commit` 这一层的修改,构成新的镜像。 + +而上面的这种写法,创建了 7 层镜像。这是完全没有意义的,而且很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。 这是很多初学 Docker 的人常犯的一个错误。 + +_Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过 127 层。_ + +上面的 `Dockerfile` 正确的写法应该是这样: + +```dockerfile +FROM debian:jessie + +RUN buildDeps='gcc libc6-dev make' \ + && apt-get update \ + && apt-get install -y $buildDeps \ + && wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \ + && mkdir -p /usr/src/redis \ + && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \ + && make -C /usr/src/redis \ + && make -C /usr/src/redis install \ + && rm -rf /var/lib/apt/lists/* \ + && rm redis.tar.gz \ + && rm -r /usr/src/redis \ + && apt-get purge -y --auto-remove $buildDeps +``` + +首先,之前所有的命令只有一个目的,就是编译、安装 redis 可执行文件。因此没有必要建立很多层,这只是一层的事情。因此,这里没有使用很多个 `RUN` 对一一对应不同的命令,而是仅仅使用一个 `RUN` 指令,并使用 `&&` 将各个所需命令串联起来。将之前的 7 层,简化为了 1 层。在撰写 Dockerfile 的时候,要经常提醒自己,这并不是在写 Shell 脚本,而是在定义每一层该如何构建。 + +并且,这里为了格式化还进行了换行。Dockerfile 支持 Shell 类的行尾添加 `\` 的命令换行方式,以及行首 `#` 进行注释的格式。良好的格式,比如换行、缩进、注释等,会让维护、排障更为容易,这是一个比较好的习惯。 + +此外,还可以看到这一组命令的最后添加了清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了 `apt` 缓存文件。这是很重要的一步,我们之前说过,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。 + +很多人初学 Docker 制作出了很臃肿的镜像的原因之一,就是忘记了每一层构建的最后一定要清理掉无关文件。 + +### COPY(复制文件) + +> 作用: +> +> **`COPY` 指令将从构建上下文目录中 `<源路径>` 的文件/目录复制到新的一层的镜像内的 `<目标路径>` 位置。** + +格式: + +- `COPY [--chown=:] <源路径>... <目标路径>` +- `COPY [--chown=:] ["<源路径1>",... "<目标路径>"]` 示例: -``` -# Comment -RUN echo 'we are running some # of cool things' +```dockerfile +COPY package.json /usr/src/app/ ``` -## 解析指令(parser directives) +`<源路径>` 可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 [`filepath.Match`](https://golang.org/pkg/path/filepath/#Match) 规则,如: -解析指令是可选的,并且会影响 Dockerfile 中后续行的处理方式。解析指令不会将图层添加到构建中,并且不会显示为构建步骤。解析指令以 `# directive=value` 的形式写入特殊类型的注释。单个指令只能使用一次。 - -一旦注释,空行或构建指令已被处理,Docker 不再查找解析指令。相反,它将任何符合解析指令格式的内容视为注释,不会尝试验证它是否可能是解析指令。因此,所有解析指令都必须位于Dockerfile 的最顶端。 - -解析指令不区分大小写。但是,约定是小写。约定还包括在任何解析指令之后的空白行。解析指令不支持续行符。 - -以下列举几个不合规范的示例。 - -因为续行符而无效: - -``` -# direc \ -tive=value +```dockerfile +COPY hom* /mydir/ +COPY hom?.txt /mydir/ ``` -因为出现两次解析指令而无效: +`<目标路径>` 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 `WORKDIR` 指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。 -``` -# directive=value1 -# directive=value2 +此外,还需要注意一点,使用 `COPY` 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。 -FROM ImageName +在使用该指令的时候还可以加上 `--chown=:` 选项来改变文件的所属用户及所属组。 + +```dockerfile +COPY --chown=55:mygroup files* /mydir/ +COPY --chown=bin files* /mydir/ +COPY --chown=1 files* /mydir/ +COPY --chown=10:11 files* /mydir/ ``` -解析指令由于出现在构建指令后,而被视为注释: +### ADD(更高级的复制文件) -``` -FROM ImageName -# directive=value +> 作用: +> +> `ADD` 指令和 `COPY` 的格式和性质基本一致。但是在 `COPY` 基础上增加了一些功能。 +> +> 比如 `<源路径>` 可以是一个 `URL`,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 `<目标路径>`去。下载后的文件权限自动设置为 `600`,如果这并不是想要的权限,那么还需要增加额外的一层 `RUN` 进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 `RUN` 指令进行解压缩。所以不如直接使用 `RUN` 指令,然后使用 `wget` 或者 `curl` 工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用。 +> +> 如果 `<源路径>` 为一个 `tar` 压缩文件的话,压缩格式为 `gzip`, `bzip2` 以及 `xz` 的情况下,`ADD` 指令将会自动解压缩这个压缩文件到 `<目标路径>` 去。 + +在某些情况下,这个自动解压缩的功能非常有用,比如官方镜像 `ubuntu` 中: + +```dockerfile +FROM scratch +ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz / +... ``` -解析指令由于出现在注释后,而被视为注释: +但在某些情况下,如果我们真的是希望复制个压缩文件进去,而不解压缩,这时就不可以使用 `ADD` 命令了。 -``` -# About my dockerfile -# directive=value -FROM ImageName +在 Docker 官方的 [Dockerfile 最佳实践文档](https://yeasy.gitbooks.io/docker_practice/content/appendix/best_practices.html) 中要求,尽可能的使用 `COPY`,因为 `COPY` 的语义很明确,就是复制文件而已,而 `ADD` 则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 `ADD` 的场合,就是所提及的需要自动解压缩的场合。 + +另外需要注意的是,`ADD` 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。 + +因此在 `COPY` 和 `ADD` 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 `COPY` 指令,仅在需要自动解压缩的场合使用 `ADD`。 + +在使用该指令的时候还可以加上 `--chown=:` 选项来改变文件的所属用户及所属组。 + +```dockerfile +ADD --chown=55:mygroup files* /mydir/ +ADD --chown=bin files* /mydir/ +ADD --chown=1 files* /mydir/ +ADD --chown=10:11 files* /mydir/ ``` -因为无法识别,未知的指令被视为注释。此外,因为前一个未知指定被视为注释,后一个指令相当于出现在注释后,而也被视为了注释: +### CMD(容器启动命令) -``` -# unknowndirective=value -# knowndirective=value +> 作用: +> +> 之前介绍容器的时候曾经说过,Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。`CMD` 指令就是用于指定默认的容器主进程的启动命令的。 + +`CMD` 指令的格式和 `RUN` 相似,也是两种格式: + +- `shell` 格式:`CMD <命令>` +- `exec` 格式:`CMD ["可执行文件", "参数1", "参数2"...]` +- 参数列表格式:`CMD ["参数1", "参数2"...]`。在指定了 `ENTRYPOINT` 指令后,用 `CMD` 指定具体的参数。 + +在运行时可以指定新的命令来替代镜像设置中的这个默认命令,比如,`ubuntu` 镜像默认的 `CMD` 是 `/bin/bash`,如果我们直接 `docker run -it ubuntu` 的话,会直接进入 `bash`。我们也可以在运行时指定运行别的命令,如 `docker run -it ubuntu cat /etc/os-release`。这就是用 `cat /etc/os-release` 命令替换了默认的 `/bin/bash` 命令了,输出了系统版本信息。 + +在指令格式上,一般推荐使用 `exec` 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 `"`,而不要使用单引号。 + +如果使用 `shell` 格式的话,实际的命令会被包装为 `sh -c` 的参数的形式进行执行。比如: + +```dockerfile +CMD echo $HOME ``` -解析器指令中允许使用非分行空白。因此,以下几行都是一致对待的: +在实际执行中,会将其变更为: -``` -#directive=value -# directive =value -# directive= value -# directive = value -# dIrEcTiVe=value +```dockerfile +CMD [ "sh", "-c", "echo $HOME" ] ``` -### escape +这就是为什么我们可以使用环境变量的原因,因为这些环境变量会被 shell 进行解析处理。 -``` -# escape=\ (backslash) +提到 `CMD` 就不得不提容器中应用在前台执行和后台执行的问题。这是初学者常出现的一个混淆。 + +Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用 upstart/systemd 去启动后台服务,容器内没有后台服务的概念。 + +一些初学者将 `CMD` 写为: + +```dockerfile +CMD service nginx start ``` -``` -# escape=` (backtick) +然后发现容器执行后就立即退出了。甚至在容器内去使用 `systemctl` 命令结果却发现根本执行不了。这就是因为没有搞明白前台、后台的概念,没有区分容器和虚拟机的差异,依旧在以传统虚拟机的角度去理解容器。 + +对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。 + +而使用 `service nginx start` 命令,则是希望 upstart 来以后台守护进程形式启动 `nginx` 服务。而刚才说了 `CMD service nginx start` 会被理解为 `CMD [ "sh", "-c", "service nginx start"]`,因此主进程实际上是 `sh`。那么当 `service nginx start` 命令结束后,`sh` 也就结束了,`sh` 作为主进程退出了,自然就会令容器退出。 + +正确的做法是直接执行 `nginx` 可执行文件,并且要求以前台形式运行。比如: + +```dockerfile +CMD ["nginx", "-g", "daemon off;"] ``` -在 Dockerfile 中, \ No newline at end of file +### ENTRYPOINT(入口点) + +`ENTRYPOINT` 的格式和 `RUN` 指令格式一样,分为 `exec` 格式和 `shell` 格式。 + +`ENTRYPOINT` 的目的和 `CMD` 一样,都是在指定容器启动程序及参数。`ENTRYPOINT` 在运行时也可以替代,不过比 `CMD` 要略显繁琐,需要通过 `docker run` 的参数 `--entrypoint` 来指定。 + +当指定了 `ENTRYPOINT` 后,`CMD` 的含义就发生了改变,不再是直接的运行其命令,而是将 `CMD` 的内容作为参数传给 `ENTRYPOINT` 指令,换句话说实际执行时,将变为: + +```bash + "" +``` + +那么有了 `CMD` 后,为什么还要有 `ENTRYPOINT` 呢?这种 ` ""` 有什么好处么?让我们来看几个场景。 + +#### 场景一:让镜像变成像命令一样使用 + +假设我们需要一个得知自己当前公网 IP 的镜像,那么可以先用 `CMD` 来实现: + +```dockerfile +FROM ubuntu:18.04 +RUN apt-get update \ + && apt-get install -y curl \ + && rm -rf /var/lib/apt/lists/* +CMD [ "curl", "-s", "https://ip.cn" ] +``` + +假如我们使用 `docker build -t myip .` 来构建镜像的话,如果我们需要查询当前公网 IP,只需要执行: + +```bash +$ docker run myip +当前 IP:61.148.226.66 来自:北京市 联通 +``` + +嗯,这么看起来好像可以直接把镜像当做命令使用了,不过命令总有参数,如果我们希望加参数呢?比如从上面的 `CMD` 中可以看到实质的命令是 `curl`,那么如果我们希望显示 HTTP 头信息,就需要加上 `-i` 参数。那么我们可以直接加 `-i` 参数给 `docker run myip` 么? + +```bash +$ docker run myip -i +docker: Error response from daemon: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n". +``` + +我们可以看到可执行文件找不到的报错,`executable file not found`。之前我们说过,跟在镜像名后面的是 `command`,运行时会替换 `CMD` 的默认值。因此这里的 `-i` 替换了原来的 `CMD`,而不是添加在原来的 `curl -s https://ip.cn` 后面。而 `-i` 根本不是命令,所以自然找不到。 + +那么如果我们希望加入 `-i` 这参数,我们就必须重新完整的输入这个命令: + +```bash +$ docker run myip curl -s https://ip.cn -i +``` + +这显然不是很好的解决方案,而使用 `ENTRYPOINT` 就可以解决这个问题。现在我们重新用 `ENTRYPOINT` 来实现这个镜像: + +```dockerfile +FROM ubuntu:18.04 +RUN apt-get update \ + && apt-get install -y curl \ + && rm -rf /var/lib/apt/lists/* +ENTRYPOINT [ "curl", "-s", "https://ip.cn" ] +``` + +这次我们再来尝试直接使用 `docker run myip -i`: + +```bash +$ docker run myip +当前 IP:61.148.226.66 来自:北京市 联通 + +$ docker run myip -i +HTTP/1.1 200 OK +Server: nginx/1.8.0 +Date: Tue, 22 Nov 2016 05:12:40 GMT +Content-Type: text/html; charset=UTF-8 +Vary: Accept-Encoding +X-Powered-By: PHP/5.6.24-1~dotdeb+7.1 +X-Cache: MISS from cache-2 +X-Cache-Lookup: MISS from cache-2:80 +X-Cache: MISS from proxy-2_6 +Transfer-Encoding: chunked +Via: 1.1 cache-2:80, 1.1 proxy-2_6:8006 +Connection: keep-alive + +当前 IP:61.148.226.66 来自:北京市 联通 +``` + +可以看到,这次成功了。这是因为当存在 `ENTRYPOINT` 后,`CMD` 的内容将会作为参数传给 `ENTRYPOINT`,而这里 `-i` 就是新的 `CMD`,因此会作为参数传给 `curl`,从而达到了我们预期的效果。 + +#### 场景二:应用运行前的准备工作 + +启动容器就是启动主进程,但有些时候,启动主进程前,需要一些准备工作。 + +比如 `mysql` 类的数据库,可能需要一些数据库配置、初始化的工作,这些工作要在最终的 mysql 服务器运行之前解决。 + +此外,可能希望避免使用 `root` 用户去启动服务,从而提高安全性,而在启动服务前还需要以 `root` 身份执行一些必要的准备工作,最后切换到服务用户身份启动服务。或者除了服务外,其它命令依旧可以使用 `root` 身份执行,方便调试等。 + +这些准备工作是和容器 `CMD` 无关的,无论 `CMD` 为什么,都需要事先进行一个预处理的工作。这种情况下,可以写一个脚本,然后放入 `ENTRYPOINT` 中去执行,而这个脚本会将接到的参数(也就是 ``)作为命令,在脚本最后执行。比如官方镜像 `redis` 中就是这么做的: + +```dockerfile +FROM alpine:3.4 +... +RUN addgroup -S redis && adduser -S -G redis redis +... +ENTRYPOINT ["docker-entrypoint.sh"] + +EXPOSE 6379 +CMD [ "redis-server" ] +``` + +可以看到其中为了 redis 服务创建了 redis 用户,并在最后指定了 `ENTRYPOINT` 为 `docker-entrypoint.sh` 脚本。 + +```bash +#!/bin/sh +... +# allow the container to be started with `--user` +if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then + chown -R redis . + exec su-exec redis "$0" "$@" +fi + +exec "$@" +``` + +该脚本的内容就是根据 `CMD` 的内容来判断,如果是 `redis-server` 的话,则切换到 `redis` 用户身份启动服务器,否则依旧使用 `root` 身份执行。比如: + +```bash +$ docker run -it redis id +uid=0(root) gid=0(root) groups=0(root) +``` + +### ENV(设置环境变量) + +> 作用:`ENV` 指令用于设置环境变量。无论是后面的其它指令,如 `RUN`,还是运行时的应用,都可以直接使用这里定义的环境变量。 + +格式: + +- `ENV ` +- `ENV = =...` + +示例 1: + +```dockerfile +ENV VERSION=1.0 DEBUG=on \ + NAME="Happy Feet" +``` + +这个例子中演示了如何换行,以及对含有空格的值用双引号括起来的办法,这和 Shell 下的行为是一致的。 + +示例 2: + +定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。比如在官方 `node` 镜像 `Dockerfile` 中,就有类似这样的代码: + +```dockerfile +ENV NODE_VERSION 7.2.0 + +RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \ + && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \ + && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \ + && grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \ + && tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \ + && rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \ + && ln -s /usr/local/bin/node /usr/local/bin/nodejs +``` + +在这里先定义了环境变量 `NODE_VERSION`,其后的 `RUN` 这层里,多次使用 `$NODE_VERSION` 来进行操作定制。可以看到,将来升级镜像构建版本的时候,只需要更新 `7.2.0` 即可,`Dockerfile` 构建维护变得更轻松了。 + +下列指令可以支持环境变量展开: `ADD`、`COPY`、`ENV`、`EXPOSE`、`LABEL`、`USER`、`WORKDIR`、`VOLUME`、`STOPSIGNAL`、`ONBUILD`。 + +可以从这个指令列表里感觉到,环境变量可以使用的地方很多,很强大。通过环境变量,我们可以让一份 `Dockerfile` 制作更多的镜像,只需使用不同的环境变量即可。 + +### ARG(构建参数) + +> 作用: +> +> `Dockerfile` 中的 `ARG` 指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令 `docker build` 中用 `--build-arg <参数名>=<值>` 来覆盖。 +> +> 构建参数和 `ENV` 的效果一样,都是设置环境变量。所不同的是,`ARG` 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 `ARG` 保存密码之类的信息,因为 `docker history` 还是可以看到所有值的。 + +格式:`ARG <参数名>[=<默认值>]` + +在 1.13 之前的版本,要求 `--build-arg` 中的参数名,必须在 `Dockerfile` 中用 `ARG` 定义过了,换句话说,就是 `--build-arg` 指定的参数,必须在 `Dockerfile` 中使用了。如果对应参数没有被使用,则会报错退出构建。从 1.13 开始,这种严格的限制被放开,不再报错退出,而是显示警告信息,并继续构建。这对于使用 CI 系统,用同样的构建流程构建不同的 `Dockerfile` 的时候比较有帮助,避免构建命令必须根据每个 Dockerfile 的内容修改。 + +### VOLUME(定义匿名卷) + +格式: + +- `VOLUME ["<路径1>", "<路径2>"...]` +- `VOLUME <路径>` + +之前我们说过,容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,后面的章节我们会进一步介绍 Docker 卷的概念。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 `Dockerfile` 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。 + +```dockerfile +VOLUME /data +``` + +这里的 `/data` 目录就会在运行时自动挂载为匿名卷,任何向 `/data` 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。比如: + +```dockerfile +docker run -d -v mydata:/data xxxx +``` + +在这行命令中,就使用了 `mydata` 这个命名卷挂载到了 `/data` 这个位置,替代了 `Dockerfile` 中定义的匿名卷的挂载配置。 + +### EXPOSE(暴露端口) + +> 作用: +> +> `EXPOSE` 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 `docker run -P` 时,会自动随机映射 `EXPOSE` 的端口。 +> +> 要将 `EXPOSE` 和在运行时使用 `-p <宿主端口>:<容器端口>` 区分开来。`-p`,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 `EXPOSE` 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。 + +格式:`EXPOSE <端口1> [<端口2>...]`。 + +### WORKDIR(指定工作目录) + +> 作用: +> +> 使用 `WORKDIR` 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,`WORKDIR` 会帮你建立目录。 + +格式:`WORKDIR <工作目录路径>`。 + +示例 1: + +之前提到一些初学者常犯的错误是把 `Dockerfile` 等同于 Shell 脚本来书写,这种错误的理解还可能会导致出现下面这样的错误: + +```dockerfile +RUN cd /app +RUN echo "hello" > world.txt +``` + +如果将这个 `Dockerfile` 进行构建镜像运行后,会发现找不到 `/app/world.txt` 文件,或者其内容不是 `hello`。原因其实很简单,在 Shell 中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态,会直接影响后一个命令;而在 `Dockerfile` 中,这两行 `RUN` 命令的执行环境根本不同,是两个完全不同的容器。这就是对 `Dockerfile` 构建分层存储的概念不了解所导致的错误。 + +之前说过每一个 `RUN` 都是启动一个容器、执行命令、然后提交存储层文件变更。第一层 `RUN cd /app` 的执行仅仅是当前进程的工作目录变更,一个内存上的变化而已,其结果不会造成任何文件变更。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。 + +因此如果需要改变以后各层的工作目录的位置,那么应该使用 `WORKDIR` 指令。 + +### USER(指定当前用户) + +> 作用: +> +> `USER` 指令和 `WORKDIR` 相似,都是改变环境状态并影响以后的层。`WORKDIR` 是改变工作目录,`USER` 则是改变之后层的执行 `RUN`, `CMD` 以及 `ENTRYPOINT` 这类命令的身份。 +> +> 当然,和 `WORKDIR` 一样,`USER` 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。 + +格式:`USER <用户名>[:<用户组>]` + +示例 1: + +```dockerfile +RUN groupadd -r redis && useradd -r -g redis redis +USER redis +RUN [ "redis-server" ] +``` + +如果以 `root` 执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用 `su`或者 `sudo`,这些都需要比较麻烦的配置,而且在 TTY 缺失的环境下经常出错。建议使用 [`gosu`](https://github.com/tianon/gosu)。 + +```dockerfile +# 建立 redis 用户,并使用 gosu 换另一个用户执行命令 +RUN groupadd -r redis && useradd -r -g redis redis +# 下载 gosu +RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64" \ + && chmod +x /usr/local/bin/gosu \ + && gosu nobody true +# 设置 CMD,并以另外的用户执行 +CMD [ "exec", "gosu", "redis", "redis-server" ] +``` + +### HEALTHCHECK(健康检查) + +格式: + +- `HEALTHCHECK [选项] CMD <命令>`:设置检查容器健康状况的命令 +- `HEALTHCHECK NONE`:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令 + +`HEALTHCHECK` 指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12 引入的新指令。 + +在没有 `HEALTHCHECK` 指令前,Docker 引擎只可以通过容器内主进程是否退出来判断容器是否状态异常。很多情况下这没问题,但是如果程序进入死锁状态,或者死循环状态,应用进程并不退出,但是该容器已经无法提供服务了。在 1.12 以前,Docker 不会检测到容器的这种状态,从而不会重新调度,导致可能会有部分容器已经无法提供服务了却还在接受用户请求。 + +而自 1.12 之后,Docker 提供了 `HEALTHCHECK` 指令,通过该指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反应容器实际状态。 + +当在一个镜像指定了 `HEALTHCHECK` 指令后,用其启动容器,初始状态会为 `starting`,在 `HEALTHCHECK` 指令检查成功后变为 `healthy`,如果连续一定次数失败,则会变为 `unhealthy`。 + +`HEALTHCHECK` 支持下列选项: + +- `--interval=<间隔>`:两次健康检查的间隔,默认为 30 秒; +- `--timeout=<时长>`:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒; +- `--retries=<次数>`:当连续失败指定次数后,则将容器状态视为 `unhealthy`,默认 3 次。 + +和 `CMD`, `ENTRYPOINT` 一样,`HEALTHCHECK` 只可以出现一次,如果写了多个,只有最后一个生效。 + +在 `HEALTHCHECK [选项] CMD` 后面的命令,格式和 `ENTRYPOINT` 一样,分为 `shell` 格式,和 `exec` 格式。命令的返回值决定了该次健康检查的成功与否:`0`:成功;`1`:失败;`2`:保留,不要使用这个值。 + +假设我们有个镜像是个最简单的 Web 服务,我们希望增加健康检查来判断其 Web 服务是否在正常工作,我们可以用 `curl` 来帮助判断,其 `Dockerfile` 的 `HEALTHCHECK` 可以这么写: + +```dockerfile +FROM nginx +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* +HEALTHCHECK --interval=5s --timeout=3s \ + CMD curl -fs http://localhost/ || exit 1 +``` + +这里我们设置了每 5 秒检查一次(这里为了试验所以间隔非常短,实际应该相对较长),如果健康检查命令超过 3 秒没响应就视为失败,并且使用 `curl -fs http://localhost/ || exit 1` 作为健康检查命令。 + +使用 `docker build` 来构建这个镜像: + +```bash +$ docker build -t myweb:v1 . +``` + +构建好了后,我们启动一个容器: + +```bash +$ docker run -d --name web -p 80:80 myweb:v1 +``` + +当运行该镜像后,可以通过 `docker container ls` 看到最初的状态为 `(health: starting)`: + +```bash +$ docker container ls +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 3 seconds ago Up 2 seconds (health: starting) 80/tcp, 443/tcp web +``` + +在等待几秒钟后,再次 `docker container ls`,就会看到健康状态变化为了 `(healthy)`: + +```bash +$ docker container ls +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 18 seconds ago Up 16 seconds (healthy) 80/tcp, 443/tcp web +``` + +如果健康检查连续失败超过了重试次数,状态就会变为 `(unhealthy)`。 + +为了帮助排障,健康检查命令的输出(包括 `stdout` 以及 `stderr`)都会被存储于健康状态里,可以用 `docker inspect` 来查看。 + +```bash +$ docker inspect --format '{{json .State.Health}}' web | python -m json.tool +{ + "FailingStreak": 0, + "Log": [ + { + "End": "2016-11-25T14:35:37.940957051Z", + "ExitCode": 0, + "Output": "\n\n\nWelcome to nginx!\n\n\n\n

Welcome to nginx!

\n

If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.

\n\n

For online documentation and support please refer to\nnginx.org.
\nCommercial support is available at\nnginx.com.

\n\n

Thank you for using nginx.

\n\n\n", + "Start": "2016-11-25T14:35:37.780192565Z" + } + ], + "Status": "healthy" +} +``` + +### ONBUILD(为他人作嫁衣裳) + +格式:`ONBUILD <其它指令>`。 + +`ONBUILD` 是一个特殊的指令,它后面跟的是其它指令,比如 `RUN`, `COPY` 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。 + +`Dockerfile` 中的其它指令都是为了定制当前镜像而准备的,唯有 `ONBUILD` 是为了帮助别人定制自己而准备的。 + +假设我们要制作 Node.js 所写的应用的镜像。我们都知道 Node.js 使用 `npm` 进行包管理,所有依赖、配置、启动信息等会放到 `package.json` 文件里。在拿到程序代码后,需要先进行 `npm install` 才可以获得所有需要的依赖。然后就可以通过 `npm start`来启动应用。因此,一般来说会这样写 `Dockerfile`: + +```dockerfile +FROM node:slim +RUN mkdir /app +WORKDIR /app +COPY ./package.json /app +RUN [ "npm", "install" ] +COPY . /app/ +CMD [ "npm", "start" ] +``` + +把这个 `Dockerfile` 放到 Node.js 项目的根目录,构建好镜像后,就可以直接拿来启动容器运行。但是如果我们还有第二个 Node.js 项目也差不多呢?好吧,那就再把这个 `Dockerfile` 复制到第二个项目里。那如果有第三个项目呢?再复制么?文件的副本越多,版本控制就越困难,让我们继续看这样的场景维护的问题。 + +如果第一个 Node.js 项目在开发过程中,发现这个 `Dockerfile` 里存在问题,比如敲错字了、或者需要安装额外的包,然后开发人员修复了这个 `Dockerfile`,再次构建,问题解决。� 第一个项目没问题了,但是第二个项目呢?虽然最初 `Dockerfile` 是复制、粘贴自第一个项目的,但是并不会因为第一个项目修复了他们的 `Dockerfile`,而第二个项目的 `Dockerfile` 就会被自动修复。 + +那么我们可不可以做一个基础镜像,然后各个项目使用这个基础镜像呢?这样基础镜像更新,各个项目不用同步 `Dockerfile`的变化,重新构建后就继承了基础镜像的更新?好吧,可以,让我们看看这样的结果。那么上面的这个 `Dockerfile` 就会变为: + +```dockerfile +FROM node:slim +RUN mkdir /app +WORKDIR /app +CMD [ "npm", "start" ] +``` + +这里我们把项目相关的构建指令拿出来,放到子项目里去。假设这个基础镜像的名字为 `my-node` 的话,各个项目内的自己的 `Dockerfile` 就变为: + +```dockerfile +FROM my-node +COPY ./package.json /app +RUN [ "npm", "install" ] +COPY . /app/ +``` + +基础镜像变化后,各个项目都用这个 `Dockerfile` 重新构建镜像,会继承基础镜像的更新。 + +那么,问题解决了么?没有。准确说,只解决了一半。如果这个 `Dockerfile` 里面有些东西需要调整呢?比如 `npm install` 都需要加一些参数,那怎么办?这一行 `RUN` 是不可能放入基础镜像的,因为涉及到了当前项目的 `./package.json`,难道又要一个个修改么?所以说,这样制作基础镜像,只解决了原来的 `Dockerfile` 的前 4 条指令的变化问题,而后面三条指令的变化则完全没办法处理。 + +`ONBUILD` 可以解决这个问题。让我们用 `ONBUILD` 重新写一下基础镜像的 `Dockerfile`: + +```dockerfile +FROM node:slim +RUN mkdir /app +WORKDIR /app +ONBUILD COPY ./package.json /app +ONBUILD RUN [ "npm", "install" ] +ONBUILD COPY . /app/ +CMD [ "npm", "start" ] +``` + +这次我们回到原始的 `Dockerfile`,但是这次将项目相关的指令加上 `ONBUILD`,这样在构建基础镜像的时候,这三行并不会被执行。然后各个项目的 `Dockerfile` 就变成了简单地: + +```dockerfile +FROM my-node +``` + +是的,只有这么一行。当在各个项目目录中,用这个只有一行的 `Dockerfile` 构建镜像时,之前基础镜像的那三行 `ONBUILD` 就会开始执行,成功的将当前项目的代码复制进镜像、并且针对本项目执行 `npm install`,生成应用镜像。 + +## 引用和引申 + +- [Dockerfie 官方文档](https://docs.docker.com/engine/reference/builder/) +- [Dockerfile 最佳实践文档](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/) +- [Docker 官方镜像 Dockerfile](https://github.com/docker-library/docs) +- [Dockerfile 指令详解](https://yeasy.gitbooks.io/docker_practice/content/image/dockerfile/) diff --git a/docs/docker/basics/docker-image.md b/docs/docker/basics/docker-image.md new file mode 100644 index 0000000..ac6ca42 --- /dev/null +++ b/docs/docker/basics/docker-image.md @@ -0,0 +1,491 @@ +# Docker 镜像 + + + +- [获取镜像](#获取镜像) + - [运行](#运行) +- [列出镜像](#列出镜像) + - [镜像体积](#镜像体积) + - [虚悬镜像](#虚悬镜像) + - [中间层镜像](#中间层镜像) + - [列出部分镜像](#列出部分镜像) + - [以特定格式显示](#以特定格式显示) +- [删除本地镜像](#删除本地镜像) + - [用 ID、镜像名、摘要删除镜像](#用-id镜像名摘要删除镜像) + - [Untagged 和 Deleted](#untagged-和-deleted) + - [用 docker image ls 命令来配合](#用-docker-image-ls-命令来配合) + - [CentOS/RHEL 的用户需要注意的事项](#centosrhel-的用户需要注意的事项) +- [使用 Dockerfile 定制镜像](#使用-dockerfile-定制镜像) + - [构建镜像](#构建镜像) + - [镜像构建上下文(Context)](#镜像构建上下文context) + - [其它 `docker build` 的用法](#其它-docker-build-的用法) + + + +## 获取镜像 + +之前提到过,[Docker Hub](https://hub.docker.com/explore/) 上有大量的高质量的镜像可以用,这里我们就说一下怎么获取这些镜像。 + +从 Docker 镜像仓库获取镜像的命令是 `docker pull`。其命令格式为: + +```bash +docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签] +``` + +具体的选项可以通过 `docker pull --help` 命令看到,这里我们说一下镜像名称的格式。 + +- Docker 镜像仓库地址:地址的格式一般是 `<域名/IP>[:端口号]`。默认地址是 Docker Hub。 +- 仓库名:如之前所说,这里的仓库名是两段式名称,即 `<用户名>/<软件名>`。对于 Docker Hub,如果不给出用户名,则默认为 `library`,也就是官方镜像。 + +比如: + +```bash +$ docker pull ubuntu:18.04 +18.04: Pulling from library/ubuntu +bf5d46315322: Pull complete +9f13e0ac480c: Pull complete +e8988b5b3097: Pull complete +40af181810e7: Pull complete +e6f7c7e5c03e: Pull complete +Digest: sha256:147913621d9cdea08853f6ba9116c2e27a3ceffecf3b492983ae97c3d643fbbe +Status: Downloaded newer image for ubuntu:18.04 +``` + +上面的命令中没有给出 Docker 镜像仓库地址,因此将会从 Docker Hub 获取镜像。而镜像名称是 `ubuntu:18.04`,因此将会获取官方镜像 `library/ubuntu` 仓库中标签为 `18.04` 的镜像。 + +从下载过程中可以看到我们之前提及的分层存储的概念,镜像是由多层存储所构成。下载也是一层层的去下载,并非单一文件。下载过程中给出了每一层的 ID 的前 12 位。并且下载结束后,给出该镜像完整的 `sha256` 的摘要,以确保下载一致性。 + +在使用上面命令的时候,你可能会发现,你所看到的层 ID 以及 `sha256` 的摘要和这里的不一样。这是因为官方镜像是一直在维护的,有任何新的 bug,或者版本更新,都会进行修复再以原来的标签发布,这样可以确保任何使用这个标签的用户可以获得更安全、更稳定的镜像。 + +*如果从 Docker Hub 下载镜像非常缓慢,可以参照 镜像加速器 一节配置加速器。* + +### 运行 + +有了镜像后,我们就能够以这个镜像为基础启动并运行一个容器。以上面的 `ubuntu:18.04` 为例,如果我们打算启动里面的 `bash` 并且进行交互式操作的话,可以执行下面的命令。 + +```bash +$ docker run -it --rm \ + ubuntu:18.04 \ + bash + +root@e7009c6ce357:/# cat /etc/os-release +NAME="Ubuntu" +VERSION="18.04.1 LTS (Bionic Beaver)" +ID=ubuntu +ID_LIKE=debian +PRETTY_NAME="Ubuntu 18.04.1 LTS" +VERSION_ID="18.04" +HOME_URL="https://www.ubuntu.com/" +SUPPORT_URL="https://help.ubuntu.com/" +BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" +PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" +VERSION_CODENAME=bionic +UBUNTU_CODENAME=bionic +``` + +`docker run` 就是运行容器的命令,具体格式我们会在 [容器](https://yeasy.gitbooks.io/docker_practice/content/container) 一节进行详细讲解,我们这里简要的说明一下上面用到的参数。 + +- `-it`:这是两个参数,一个是 `-i`:交互式操作,一个是 `-t` 终端。我们这里打算进入 `bash` 执行一些命令并查看返回结果,因此我们需要交互式终端。 +- `--rm`:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动 `docker rm`。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用 `--rm` 可以避免浪费空间。 +- `ubuntu:18.04`:这是指用 `ubuntu:18.04` 镜像为基础来启动容器。 +- `bash`:放在镜像名后的是**命令**,这里我们希望有个交互式 Shell,因此用的是 `bash`。 + +进入容器后,我们可以在 Shell 下操作,执行任何所需的命令。这里,我们执行了 `cat /etc/os-release`,这是 Linux 常用的查看当前系统版本的命令,从返回的结果可以看到容器内是 `Ubuntu 18.04.1 LTS` 系统。 + +最后我们通过 `exit` 退出了这个容器。 + +## 列出镜像 + +要想列出已经下载下来的镜像,可以使用 `docker image ls` 命令。 + +```bash +$ docker image ls +REPOSITORY TAG IMAGE ID CREATED SIZE +redis latest 5f515359c7f8 5 days ago 183 MB +nginx latest 05a60462f8ba 5 days ago 181 MB +mongo 3.2 fe9198c04d62 5 days ago 342 MB + 00285df0df87 5 days ago 342 MB +ubuntu 18.04 f753707788c5 4 weeks ago 127 MB +ubuntu latest f753707788c5 4 weeks ago 127 MB +ubuntu 14.04 1e0c3dd64ccd 4 weeks ago 188 MB +``` + +列表包含了 `仓库名`、`标签`、`镜像 ID`、`创建时间` 以及 `所占用的空间`。 + +其中仓库名、标签在之前的基础概念章节已经介绍过了。**镜像 ID** 则是镜像的唯一标识,一个镜像可以对应多个**标签**。因此,在上面的例子中,我们可以看到 `ubuntu:18.04` 和 `ubuntu:latest` 拥有相同的 ID,因为它们对应的是同一个镜像。 + +### 镜像体积 + +如果仔细观察,会注意到,这里标识的所占用空间和在 Docker Hub 上看到的镜像大小不同。比如,`ubuntu:18.04` 镜像大小,在这里是 `127 MB`,但是在 [Docker Hub](https://hub.docker.com/r/library/ubuntu/tags/) 显示的却是 `50 MB`。这是因为 Docker Hub 中显示的体积是压缩后的体积。在镜像下载和上传过程中镜像是保持着压缩状态的,因此 Docker Hub 所显示的大小是网络传输中更关心的流量大小。而 `docker image ls` 显示的是镜像下载到本地后,展开的大小,准确说,是展开后的各层所占空间的总和,因为镜像到本地后,查看空间的时候,更关心的是本地磁盘空间占用的大小。 + +另外一个需要注意的问题是,`docker image ls` 列表中的镜像体积总和并非是所有镜像实际硬盘消耗。由于 Docker 镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层。由于 Docker 使用 Union FS,相同的层只需要保存一份即可,因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。 + +你可以通过以下命令来便捷的查看镜像、容器、数据卷所占用的空间。 + +```bash +$ docker system df + +TYPE TOTAL ACTIVE SIZE RECLAIMABLE +Images 24 0 1.992GB 1.992GB (100%) +Containers 1 0 62.82MB 62.82MB (100%) +Local Volumes 9 0 652.2MB 652.2MB (100%) +Build Cache 0B 0B +``` + +### 虚悬镜像 + +上面的镜像列表中,还可以看到一个特殊的镜像,这个镜像既没有仓库名,也没有标签,均为 ``。: + +```bash + 00285df0df87 5 days ago 342 MB +``` + +这个镜像原本是有镜像名和标签的,原来为 `mongo:3.2`,随着官方镜像维护,发布了新版本后,重新 `docker pull mongo:3.2` 时,`mongo:3.2` 这个镜像名被转移到了新下载的镜像身上,而旧的镜像上的这个名称则被取消,从而成为了 ``。除了 `docker pull` 可能导致这种情况,`docker build` 也同样可以导致这种现象。由于新旧镜像同名,旧镜像名称被取消,从而出现仓库名、标签均为 `` 的镜像。这类无标签镜像也被称为 **虚悬镜像(dangling image)** ,可以用下面的命令专门显示这类镜像: + +```bash +$ docker image ls -f dangling=true +REPOSITORY TAG IMAGE ID CREATED SIZE + 00285df0df87 5 days ago 342 MB +``` + +一般来说,虚悬镜像已经失去了存在的价值,是可以随意删除的,可以用下面的命令删除。 + +```bash +$ docker image prune +``` + +### 中间层镜像 + +为了加速镜像构建、重复利用资源,Docker 会利用 **中间层镜像**。所以在使用一段时间后,可能会看到一些依赖的中间层镜像。默认的 `docker image ls` 列表中只会显示顶层镜像,如果希望显示包括中间层镜像在内的所有镜像的话,需要加 `-a` 参数。 + +```bash +$ docker image ls -a +``` + +这样会看到很多无标签的镜像,与之前的虚悬镜像不同,这些无标签的镜像很多都是中间层镜像,是其它镜像所依赖的镜像。这些无标签镜像不应该删除,否则会导致上层镜像因为依赖丢失而出错。实际上,这些镜像也没必要删除,因为之前说过,相同的层只会存一遍,而这些镜像是别的镜像的依赖,因此并不会因为它们被列出来而多存了一份,无论如何你也会需要它们。只要删除那些依赖它们的镜像后,这些依赖的中间层镜像也会被连带删除。 + +### 列出部分镜像 + +不加任何参数的情况下,`docker image ls` 会列出所有顶级镜像,但是有时候我们只希望列出部分镜像。`docker image ls` 有好几个参数可以帮助做到这个事情。 + +根据仓库名列出镜像 + +```bash +$ docker image ls ubuntu +REPOSITORY TAG IMAGE ID CREATED SIZE +ubuntu 18.04 f753707788c5 4 weeks ago 127 MB +ubuntu latest f753707788c5 4 weeks ago 127 MB +ubuntu 14.04 1e0c3dd64ccd 4 weeks ago 188 MB +``` + +列出特定的某个镜像,也就是说指定仓库名和标签 + +```bash +$ docker image ls ubuntu:18.04 +REPOSITORY TAG IMAGE ID CREATED SIZE +ubuntu 18.04 f753707788c5 4 weeks ago 127 MB +``` + +除此以外,`docker image ls` 还支持强大的过滤器参数 `--filter`,或者简写 `-f`。之前我们已经看到了使用过滤器来列出虚悬镜像的用法,它还有更多的用法。比如,我们希望看到在 `mongo:3.2` 之后建立的镜像,可以用下面的命令: + +```bash +$ docker image ls -f since=mongo:3.2 +REPOSITORY TAG IMAGE ID CREATED SIZE +redis latest 5f515359c7f8 5 days ago 183 MB +nginx latest 05a60462f8ba 5 days ago 181 MB +``` + +想查看某个位置之前的镜像也可以,只需要把 `since` 换成 `before` 即可。 + +此外,如果镜像构建时,定义了 `LABEL`,还可以通过 `LABEL` 来过滤。 + +```bash +$ docker image ls -f label=com.example.version=0.1 +... +``` + +### 以特定格式显示 + +默认情况下,`docker image ls` 会输出一个完整的表格,但是我们并非所有时候都会需要这些内容。比如,刚才删除虚悬镜像的时候,我们需要利用 `docker image ls` 把所有的虚悬镜像的 ID 列出来,然后才可以交给 `docker image rm` 命令作为参数来删除指定的这些镜像,这个时候就用到了 `-q` 参数。 + +```bash +$ docker image ls -q +5f515359c7f8 +05a60462f8ba +fe9198c04d62 +00285df0df87 +f753707788c5 +f753707788c5 +1e0c3dd64ccd +``` + +`--filter` 配合 `-q` 产生出指定范围的 ID 列表,然后送给另一个 `docker` 命令作为参数,从而针对这组实体成批的进行某种操作的做法在 Docker 命令行使用过程中非常常见,不仅仅是镜像,将来我们会在各个命令中看到这类搭配以完成很强大的功能。因此每次在文档看到过滤器后,可以多注意一下它们的用法。 + +另外一些时候,我们可能只是对表格的结构不满意,希望自己组织列;或者不希望有标题,这样方便其它程序解析结果等,这就用到了 [Go 的模板语法](https://gohugo.io/templates/go-templates/)。 + +比如,下面的命令会直接列出镜像结果,并且只包含镜像ID和仓库名: + +```bash +$ docker image ls --format "{{.ID}}: {{.Repository}}" +5f515359c7f8: redis +05a60462f8ba: nginx +fe9198c04d62: mongo +00285df0df87: +f753707788c5: ubuntu +f753707788c5: ubuntu +1e0c3dd64ccd: ubuntu +``` + +或者打算以表格等距显示,并且有标题行,和默认一样,不过自己定义列: + +```bash +$ docker image ls --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}" +IMAGE ID REPOSITORY TAG +5f515359c7f8 redis latest +05a60462f8ba nginx latest +fe9198c04d62 mongo 3.2 +00285df0df87 +f753707788c5 ubuntu 18.04 +f753707788c5 ubuntu latest +1e0c3dd64ccd ubuntu 14.04 +``` + +## 删除本地镜像 + +如果要删除本地的镜像,可以使用 `docker image rm` 命令,其格式为: + +``` +$ docker image rm [选项] <镜像1> [<镜像2> ...] +``` + +### 用 ID、镜像名、摘要删除镜像 + +其中,`<镜像>` 可以是 `镜像短 ID`、`镜像长 ID`、`镜像名` 或者 `镜像摘要`。 + +比如我们有这么一些镜像: + +```bash +$ docker image ls +REPOSITORY TAG IMAGE ID CREATED SIZE +centos latest 0584b3d2cf6d 3 weeks ago 196.5 MB +redis alpine 501ad78535f0 3 weeks ago 21.03 MB +docker latest cf693ec9b5c7 3 weeks ago 105.1 MB +nginx latest e43d811ce2f4 5 weeks ago 181.5 MB +``` + +我们可以用镜像的完整 ID,也称为 `长 ID`,来删除镜像。使用脚本的时候可能会用长 ID,但是人工输入就太累了,所以更多的时候是用 `短 ID` 来删除镜像。`docker image ls` 默认列出的就已经是短 ID 了,一般取前3个字符以上,只要足够区分于别的镜像就可以了。 + +比如这里,如果我们要删除 `redis:alpine` 镜像,可以执行: + +```bash +$ docker image rm 501 +Untagged: redis:alpine +Untagged: redis@sha256:f1ed3708f538b537eb9c2a7dd50dc90a706f7debd7e1196c9264edeea521a86d +Deleted: sha256:501ad78535f015d88872e13fa87a828425117e3d28075d0c117932b05bf189b7 +Deleted: sha256:96167737e29ca8e9d74982ef2a0dda76ed7b430da55e321c071f0dbff8c2899b +Deleted: sha256:32770d1dcf835f192cafd6b9263b7b597a1778a403a109e2cc2ee866f74adf23 +Deleted: sha256:127227698ad74a5846ff5153475e03439d96d4b1c7f2a449c7a826ef74a2d2fa +Deleted: sha256:1333ecc582459bac54e1437335c0816bc17634e131ea0cc48daa27d32c75eab3 +Deleted: sha256:4fc455b921edf9c4aea207c51ab39b10b06540c8b4825ba57b3feed1668fa7c7 +``` + +我们也可以用`镜像名`,也就是 `<仓库名>:<标签>`,来删除镜像。 + +```bash +$ docker image rm centos +Untagged: centos:latest +Untagged: centos@sha256:b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c +Deleted: sha256:0584b3d2cf6d235ee310cf14b54667d889887b838d3f3d3033acd70fc3c48b8a +Deleted: sha256:97ca462ad9eeae25941546209454496e1d66749d53dfa2ee32bf1faabd239d38 +``` + +当然,更精确的是使用 `镜像摘要` 删除镜像。 + +```bash +$ docker image ls --digests +REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE +node slim sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228 6e0c4c8e3913 3 weeks ago 214 MB + +$ docker image rm node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228 +Untagged: node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228 +``` + +### Untagged 和 Deleted + +如果观察上面这几个命令的运行输出信息的话,你会注意到删除行为分为两类,一类是 `Untagged`,另一类是 `Deleted`。我们之前介绍过,镜像的唯一标识是其 ID 和摘要,而一个镜像可以有多个标签。 + +因此当我们使用上面命令删除镜像的时候,实际上是在要求删除某个标签的镜像。所以首先需要做的是将满足我们要求的所有镜像标签都取消,这就是我们看到的 `Untagged` 的信息。因为一个镜像可以对应多个标签,因此当我们删除了所指定的标签后,可能还有别的标签指向了这个镜像,如果是这种情况,那么 `Delete` 行为就不会发生。所以并非所有的 `docker image rm`都会产生删除镜像的行为,有可能仅仅是取消了某个标签而已。 + +当该镜像所有的标签都被取消了,该镜像很可能会失去了存在的意义,因此会触发删除行为。镜像是多层存储结构,因此在删除的时候也是从上层向基础层方向依次进行判断删除。镜像的多层结构让镜像复用变动非常容易,因此很有可能某个其它镜像正依赖于当前镜像的某一层。这种情况,依旧不会触发删除该层的行为。直到没有任何层依赖当前层时,才会真实的删除当前层。这就是为什么,有时候会奇怪,为什么明明没有别的标签指向这个镜像,但是它还是存在的原因,也是为什么有时候会发现所删除的层数和自己 `docker pull` 看到的层数不一样的源。 + +除了镜像依赖以外,还需要注意的是容器对镜像的依赖。如果有用这个镜像启动的容器存在(即使容器没有运行),那么同样不可以删除这个镜像。之前讲过,容器是以镜像为基础,再加一层容器存储层,组成这样的多层存储结构去运行的。因此该镜像如果被这个容器所依赖的,那么删除必然会导致故障。如果这些容器是不需要的,应该先将它们删除,然后再来删除镜像。 + +### 用 docker image ls 命令来配合 + +像其它可以承接多个实体的命令一样,可以使用 `docker image ls -q` 来配合使用 `docker image rm`,这样可以成批的删除希望删除的镜像。我们在“镜像列表”章节介绍过很多过滤镜像列表的方式都可以拿过来使用。 + +比如,我们需要删除所有仓库名为 `redis` 的镜像: + +```bash +$ docker image rm $(docker image ls -q redis) +``` + +或者删除所有在 `mongo:3.2` 之前的镜像: + +```bash +$ docker image rm $(docker image ls -q -f before=mongo:3.2) +``` + +充分利用你的想象力和 Linux 命令行的强大,你可以完成很多非常赞的功能。 + +### CentOS/RHEL 的用户需要注意的事项 + +在 Ubuntu/Debian 上有 `UnionFS` 可以使用,如 `aufs` 或者 `overlay2`,而 CentOS 和 RHEL 的内核中没有相关驱动。因此对于这类系统,一般使用 `devicemapper` 驱动利用 LVM 的一些机制来模拟分层存储。这样的做法除了性能比较差外,稳定性一般也不好,而且配置相对复杂。Docker 安装在 CentOS/RHEL 上后,会默认选择 `devicemapper`,但是为了简化配置,其 `devicemapper`是跑在一个稀疏文件模拟的块设备上,也被称为 `loop-lvm`。这样的选择是因为不需要额外配置就可以运行 Docker,这是自动配置唯一能做到的事情。但是 `loop-lvm` 的做法非常不好,其稳定性、性能更差,无论是日志还是 `docker info` 中都会看到警告信息。官方文档有明确的文章讲解了如何配置块设备给 `devicemapper` 驱动做存储层的做法,这类做法也被称为配置 `direct-lvm`。 + +除了前面说到的问题外,`devicemapper` + `loop-lvm` 还有一个缺陷,因为它是稀疏文件,所以它会不断增长。用户在使用过程中会注意到 `/var/lib/docker/devicemapper/devicemapper/data` 不断增长,而且无法控制。很多人会希望删除镜像或者可以解决这个问题,结果发现效果并不明显。原因就是这个稀疏文件的空间释放后基本不进行垃圾回收的问题。因此往往会出现即使删除了文件内容,空间却无法回收,随着使用这个稀疏文件一直在不断增长。 + +所以对于 CentOS/RHEL 的用户来说,在没有办法使用 `UnionFS` 的情况下,一定要配置 `direct-lvm` 给 `devicemapper`,无论是为了性能、稳定性还是空间利用率。 + +*或许有人注意到了 CentOS 7 中存在被 backports 回来的 overlay 驱动,不过 CentOS 里的这个驱动达不到生产环境使用的稳定程度,所以不推荐使用。* + +## 使用 Dockerfile 定制镜像 + +从刚才的 `docker commit` 的学习中,我们可以了解到,镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。 + +Dockerfile 是一个文本文件,其内包含了一条条的**指令(Instruction)**,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。 + +还以之前定制 `nginx` 镜像为例,这次我们使用 Dockerfile 来定制。 + +在一个空白目录中,建立一个文本文件,并命名为 `Dockerfile`: + +```bash +$ mkdir mynginx +$ cd mynginx +$ touch Dockerfile +``` + +其内容为: + +```dockerfile +FROM nginx +RUN echo '

Hello, Docker!

' > /usr/share/nginx/html/index.html +``` + +这个 Dockerfile 很简单,一共就两行。涉及到了两条指令,`FROM` 和 `RUN`。 + +### 构建镜像 + +好了,让我们再回到之前定制的 nginx 镜像的 Dockerfile 来。现在我们明白了这个 Dockerfile 的内容,那么让我们来构建这个镜像吧。 + +在 `Dockerfile` 文件所在目录执行: + +```bash +$ docker build -t nginx:v3 . +Sending build context to Docker daemon 2.048 kB +Step 1 : FROM nginx + ---> e43d811ce2f4 +Step 2 : RUN echo '

Hello, Docker!

' > /usr/share/nginx/html/index.html + ---> Running in 9cdc27646c7b + ---> 44aa4490ce2c +Removing intermediate container 9cdc27646c7b +Successfully built 44aa4490ce2c +``` + +从命令的输出结果中,我们可以清晰的看到镜像的构建过程。在 `Step 2` 中,如同我们之前所说的那样,`RUN` 指令启动了一个容器 `9cdc27646c7b`,执行了所要求的命令,并最后提交了这一层 `44aa4490ce2c`,随后删除了所用到的这个容器 `9cdc27646c7b`。 + +这里我们使用了 `docker build` 命令进行镜像构建。其格式为: + +```bash +docker build [选项] <上下文路径/URL/-> +``` + +在这里我们指定了最终镜像的名称 `-t nginx:v3`,构建成功后,我们可以像之前运行 `nginx:v2` 那样来运行这个镜像,其结果会和 `nginx:v2` 一样。 + +### 镜像构建上下文(Context) + +如果注意,会看到 `docker build` 命令最后有一个 `.`。`.` 表示当前目录,而 `Dockerfile` 就在当前目录,因此不少初学者以为这个路径是在指定 `Dockerfile` 所在路径,这么理解其实是不准确的。如果对应上面的命令格式,你可能会发现,这是在指定**上下文路径**。那么什么是上下文呢? + +首先我们要理解 `docker build` 的工作原理。Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API,被称为 [Docker Remote API](https://docs.docker.com/engine/reference/api/docker_remote_api/),而如 `docker` 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 `docker` 功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。 + +当我们进行镜像构建的时候,并非所有定制都会通过 `RUN` 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 `COPY` 指令、`ADD` 指令等。而 `docker build` 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢? + +这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径,`docker build` 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。 + +如果在 `Dockerfile` 中这么写: + +```Dockerfile +COPY ./package.json /app/ +``` + +这并不是要复制执行 `docker build` 命令所在的目录下的 `package.json`,也不是复制 `Dockerfile` 所在目录下的 `package.json`,而是复制 **上下文(context)** 目录下的 `package.json`。 + +因此,`COPY` 这类指令中的源文件的路径都是*相对路径*。这也是初学者经常会问的为什么 `COPY ../package.json /app` 或者 `COPY /opt/xxxx /app` 无法工作的原因,因为这些路径已经超出了上下文的范围,Docker 引擎无法获得这些位置的文件。如果真的需要那些文件,应该将它们复制到上下文目录中去。 + +现在就可以理解刚才的命令 `docker build -t nginx:v3 .` 中的这个 `.`,实际上是在指定上下文的目录,`docker build` 命令会将该目录下的内容打包交给 Docker 引擎以帮助构建镜像。 + +如果观察 `docker build` 输出,我们其实已经看到了这个发送上下文的过程: + +```bash +$ docker build -t nginx:v3 . +Sending build context to Docker daemon 2.048 kB +... +``` + +理解构建上下文对于镜像构建是很重要的,避免犯一些不应该的错误。比如有些初学者在发现 `COPY /opt/xxxx /app` 不工作后,于是干脆将 `Dockerfile` 放到了硬盘根目录去构建,结果发现 `docker build` 执行后,在发送一个几十 GB 的东西,极为缓慢而且很容易构建失败。那是因为这种做法是在让 `docker build`打包整个硬盘,这显然是使用错误。 + +一般来说,应该会将 `Dockerfile` 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 `.gitignore` 一样的语法写一个 `.dockerignore`,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。 + +那么为什么会有人误以为 `.` 是指定 `Dockerfile` 所在目录呢?这是因为在默认情况下,如果不额外指定 `Dockerfile` 的话,会将上下文目录下的名为 `Dockerfile` 的文件作为 Dockerfile。 + +这只是默认行为,实际上 `Dockerfile` 的文件名并不要求必须为 `Dockerfile`,而且并不要求必须位于上下文目录中,比如可以用 `-f ../Dockerfile.php` 参数指定某个文件作为 `Dockerfile`。 + +当然,一般大家习惯性的会使用默认的文件名 `Dockerfile`,以及会将其置于镜像构建上下文目录中。 + +### 其它 `docker build` 的用法 + +#### 直接用 Git repo 进行构建 + +或许你已经注意到了,`docker build` 还支持从 URL 构建,比如可以直接从 Git repo 中构建: + +```bash +$ docker build https://github.com/twang2218/gitlab-ce-zh.git#:8.14 +docker build https://github.com/twang2218/gitlab-ce-zh.git\#:8.14 +Sending build context to Docker daemon 2.048 kB +Step 1 : FROM gitlab/gitlab-ce:8.14.0-ce.0 +8.14.0-ce.0: Pulling from gitlab/gitlab-ce +aed15891ba52: Already exists +773ae8583d14: Already exists +... +``` + +这行命令指定了构建所需的 Git repo,并且指定默认的 `master` 分支,构建目录为 `/8.14/`,然后 Docker 就会自己去 `git clone` 这个项目、切换到指定分支、并进入到指定目录后开始构建。 + +#### 用给定的 tar 压缩包构建 + +```bash +$ docker build http://server/context.tar.gz +``` + +如果所给出的 URL 不是个 Git repo,而是个 `tar` 压缩包,那么 Docker 引擎会下载这个包,并自动解压缩,以其作为上下文,开始构建。 + +#### 从标准输入中读取 Dockerfile 进行构建 + +```bash +docker build - < Dockerfile +``` + +或 + +```bash +cat Dockerfile | docker build - +``` + +如果标准输入传入的是文本文件,则将其视为 `Dockerfile`,并开始构建。这种形式由于直接从标准输入中读取 Dockerfile 的内容,它没有上下文,因此不可以像其他方法那样可以将本地文件 `COPY` 进镜像之类的事情。 + +#### 从标准输入中读取上下文压缩包进行构建 + +```bash +$ docker build - < context.tar.gz +``` + +如果发现标准输入的文件格式是 `gzip`、`bzip2` 以及 `xz` 的话,将会使其为上下文压缩包,直接将其展开,将里面视为上下文,并开始构建。 diff --git a/docs/docker/basics/docker-images.md b/docs/docker/basics/docker-images.md deleted file mode 100644 index 1c81928..0000000 --- a/docs/docker/basics/docker-images.md +++ /dev/null @@ -1,66 +0,0 @@ -# Docker 镜像使用 - -## 列出镜像列表 - -`docker images` 命令可用来列出本地主机上的镜像。 - -``` -# docker images -REPOSITORY TAG IMAGE ID CREATED SIZE -docker.io/maven latest 76c9ab5df55b 7 days ago 737 MB -friendlyhello latest 0c6d91e71733 8 days ago 148 MB - d1eab98cf3bd 11 days ago 311 MB -docker.io/python 2.7-slim 5541369755c4 13 days ago 139 MB -docker.io/hello-world latest f2a91732366c 4 months ago 1.85 kB -docker.io/java 8-jre e44d62cf8862 14 months ago 311 MB -docker.io/training/webapp latest 6fae60ef3446 2 years ago 349 MB -``` - -选项说明: - -* REPOSITORY:表示镜像的仓库源 -* TAG:镜像的标签 -* IMAGE ID:镜像ID -* CREATED:镜像创建时间 -* SIZE:镜像大小 - -同一仓库源可以有多个 TAG,代表这个仓库源的不同个版本,如ubuntu仓库源里,有15.10、14.04等多个不同的版本,我们使用 REPOSITORY:TAG 来定义不同的镜像。 - -如果要使用版本为14.04的ubuntu系统镜像来运行容器时,命令如下: - -``` -docker run -t -i ubuntu:14.04 /bin/bash -``` - -如果你不指定一个镜像的版本标签,例如你只使用 ubuntu,docker 将默认使用 ubuntu:latest 镜像。 - -## 查找镜像 - -可以从 Docker Hub 网站来搜索镜像,Docker Hub 网址为: https://hub.docker.com/ - -我们也可以使用 `docker search` 命令来搜索镜像。比如我们需要一个httpd的镜像来作为我们的web服务。我们可以通过 docker search 命令搜索 httpd 来寻找适合我们的镜像。 - -``` -docker search httpd -``` - -## 拉取镜像 - -当我们在本地主机上使用一个不存在的镜像时 Docker 就会自动下载这个镜像。如果我们想预先下载这个镜像,我们可以使用 `docker pull` 命令来下载它。 - -``` -# docker pull ubuntu:13.10 -13.10: Pulling from library/ubuntu -6599cadaf950: Pull complete -23eda618d451: Pull complete -f0be3084efe9: Pull complete -52de432f084b: Pull complete -a3ed95caeb02: Pull complete -Digest: sha256:15b79a6654811c8d992ebacdfbd5152fcf3d165e374e264076aa435214a947a3 -Status: Downloaded newer image for ubuntu:13.10 -``` - -下载完成后,我们可以直接使用这个镜像来运行容器。 - -## 创建镜像 - diff --git a/docs/docker/basics/docker-install.md b/docs/docker/basics/docker-install.md deleted file mode 100644 index 21daf6e..0000000 --- a/docs/docker/basics/docker-install.md +++ /dev/null @@ -1,74 +0,0 @@ -# Docker 安装 - -> 本教程基于 `Docker 1.37`。 -> -> Docker 有两种可安装版本: -> -> - [Community Edition (CE)](https://www.docker.com/community-edition/),即 Docker 社区版,适合学习。 -> - [Enterprise Edition (EE)](https://www.docker.com/enterprise-edition),即 Docker 企业版,适合企业级开发使用。 - - - -- [Windows 下安装 Docker](#windows-下安装-docker) -- [安装参考](#安装参考) - - - -## Windows 下安装 Docker - -安装 Docker Toolbox 步骤: - -(1)双击运行安装包 - -![](http://oyz7npk35.bkt.clouddn.com/images/20180920180926103056.png) - -(2)点击需要安装的程序,建议全安装 - -![](http://oyz7npk35.bkt.clouddn.com/images/20180920180926103147.png) - -(3)安装附加选项,建议选择前三个 - -![](http://oyz7npk35.bkt.clouddn.com/images/20180920180926103213.png) - -(4)安装结果 - -![](http://oyz7npk35.bkt.clouddn.com/images/20180920180926102959.png) - -### 可能遇到的问题 - -问题 1 - bash.exe 找不到 - -![](http://oyz7npk35.bkt.clouddn.com/images/20180920180926104526.png) - -打开快捷方式的属性窗口,在目标栏设置如下: - -``` -"C:\Program Files\Git\bin\bash.exe" --login -i "D:\Tools\DockerToolbox\start.sh" -``` - -![](http://oyz7npk35.bkt.clouddn.com/images/20180920180926105007.png) - -问题 2 - Hyper-V 冲突 - -![](http://oyz7npk35.bkt.clouddn.com/images/20180920180926105357.png) - -## 安装参考 - -**Enterprise Edition (EE)** - -- https://docs.docker.com/install/windows/docker-ee/ -- https://docs.docker.com/install/linux/docker-ee/ubuntu/ -- https://docs.docker.com/install/linux/docker-ee/rhel/ -- https://docs.docker.com/install/linux/docker-ee/centos/ -- https://docs.docker.com/install/linux/docker-ee/oracle/ -- https://docs.docker.com/install/linux/docker-ee/suse/ - -**Community Edition (CE)** - -- https://docs.docker.com/docker-for-mac/install/ -- https://docs.docker.com/docker-for-windows/install/ -- https://docs.docker.com/install/linux/docker-ce/ubuntu/ -- https://docs.docker.com/install/linux/docker-ce/debian/ -- https://docs.docker.com/install/linux/docker-ce/centos/ -- https://docs.docker.com/install/linux/docker-ce/fedora/ -- https://docs.docker.com/install/linux/docker-ce/binaries/ diff --git a/docs/docker/basics/docker-introduction.md b/docs/docker/basics/docker-introduction.md deleted file mode 100644 index 9d61363..0000000 --- a/docs/docker/basics/docker-introduction.md +++ /dev/null @@ -1,35 +0,0 @@ -# Docker 简介 - -## Docker 是什么 - -Docker 是开发,传输和运行应用程序的开放平台。 - -Docker 使您能够将应用程序与基础架构分开,以便快速交付软件。 - -借助 Docker,您可以像管理应用程序一样管理基础架构。通过利用 Docker 的方法快速进行运输,测试和部署代码,您可以显著缩短编写代码和在生产环境中运行代码之间的耗时。 - -## Docker 平台 - -Docker 提供了被称为容器的松散隔离环境,在环境中可以打包和运行应用程序。隔离和安全性允许您在给定主机上同时运行多个容器。容器是轻量级的,因为它们不需要管理程序的额外负载,而是直接在主机的内核中运行。这意味着您可以在给定的硬件组合上运行更多容器,而不是使用虚拟机。你甚至可以在实际上是虚拟机的主机中运行 Docker 容器! - -Docker 提供工具和平台来管理容器的生命周期: - -* 使用容器开发您的应用程序及其支持组件。 -* 容器成为分发和测试你的应用程序的单元。 -* 准备好后,将您的应用程序部署到生产环境中,作为容器或协调服务。无论您的生产环境是本地数据中心,云提供商还是两者的混合,这都是一样的。 - -## Docker 引擎 - -Docker 引擎是一个 C/S 架构的应用,它有这些主要的组件: - -服务器是一个长期运行的程序,被称为守护进程。 - -REST API 指定程序可用于与守护进程进行通信并指示其执行操作的接口。 - -命令行客户端。 - -![https://docs.docker.com/engine/images/engine-components-flow.png](https://docs.docker.com/engine/images/engine-components-flow.png) - -CLI 使用 Docker REST API 通过脚本或直接 CLI 命令来控制 Docker 守护进程或与其进行交互。许多其他 Docker 应用程序使用底层的 API 和 CLI。 - -守护进程创建并管理 Docker 对象,如镜像,容器,网络和卷。 \ No newline at end of file diff --git a/docs/docker/basics/docker-repository.md b/docs/docker/basics/docker-repository.md new file mode 100644 index 0000000..5c6a7bd --- /dev/null +++ b/docs/docker/basics/docker-repository.md @@ -0,0 +1,100 @@ +# Docker 仓库 + +仓库(Repository)是集中存放镜像的地方。 + +一个容易混淆的概念是注册服务器(Registry)。实际上注册服务器是管理仓库的具体服务器,每个服务器上可以有多个仓库,而每个仓库下面有多个镜像。从这方面来说,仓库可以被认为是一个具体的项目或目录。例如对于仓库地址 dl.dockerpool.com/ubuntu 来说,dl.dockerpool.com 是注册服务器地址,ubuntu 是仓库名。 + +## Docker Hub + +目前 Docker 官方维护了一个公共仓库 [Docker Hub](https://hub.docker.com/),其中已经包括了数量超过 15,000 的镜像。大部分需求都可以通过在 Docker Hub 中直接下载镜像来实现。 + +### 注册 + +你可以在 [https://cloud.docker.com](https://cloud.docker.com/) 免费注册一个 Docker 账号。 + +### 登录 + +可以通过执行 `docker login` 命令交互式的输入用户名及密码来完成在命令行界面登录 Docker Hub。 + +你可以通过 `docker logout` 退出登录。 + +### 拉取镜像 + +你可以通过 `docker search` 命令来查找官方仓库中的镜像,并利用 `docker pull` 命令来将它下载到本地。 + +例如以 `centos` 为关键词进行搜索: + +```bash +$ docker search centos +NAME DESCRIPTION STARS OFFICIAL AUTOMATED +centos The official build of CentOS. 465 [OK] +tianon/centos CentOS 5 and 6, created using rinse instea... 28 +blalor/centos Bare-bones base CentOS 6.5 image 6 [OK] +saltstack/centos-6-minimal 6 [OK] +tutum/centos-6.4 DEPRECATED. Use tutum/centos:6.4 instead. ... 5 [OK] +``` + +可以看到返回了很多包含关键字的镜像,其中包括镜像名字、描述、收藏数(表示该镜像的受关注程度)、是否官方创建、是否自动创建。 + +官方的镜像说明是官方项目组创建和维护的,automated 资源允许用户验证镜像的来源和内容。 + +根据是否是官方提供,可将镜像资源分为两类。 + +一种是类似 `centos` 这样的镜像,被称为基础镜像或根镜像。这些基础镜像由 Docker 公司创建、验证、支持、提供。这样的镜像往往使用单个单词作为名字。 + +还有一种类型,比如 `tianon/centos` 镜像,它是由 Docker 的用户创建并维护的,往往带有用户名称前缀。可以通过前缀 `username/` 来指定使用某个用户提供的镜像,比如 tianon 用户。 + +另外,在查找的时候通过 `--filter=stars=N` 参数可以指定仅显示收藏数量为 `N` 以上的镜像。 + +下载官方 `centos` 镜像到本地。 + +```bash +$ docker pull centos +Pulling repository centos +0b443ba03958: Download complete +539c0211cd76: Download complete +511136ea3c5a: Download complete +7064731afe90: Download complete +``` + +### 推送镜像 + +用户也可以在登录后通过 `docker push` 命令来将自己的镜像推送到 Docker Hub。 + +以下命令中的 `username` 请替换为你的 Docker 账号用户名。 + +```bash +$ docker tag ubuntu:18.04 username/ubuntu:18.04 + +$ docker image ls + +REPOSITORY TAG IMAGE ID CREATED SIZE +ubuntu 18.04 275d79972a86 6 days ago 94.6MB +username/ubuntu 18.04 275d79972a86 6 days ago 94.6MB + +$ docker push username/ubuntu:18.04 + +$ docker search username + +NAME DESCRIPTION STARS OFFICIAL AUTOMATED +username/ubuntu +``` + +### 自动创建 + +自动创建(Automated Builds)功能对于需要经常升级镜像内程序来说,十分方便。 + +有时候,用户创建了镜像,安装了某个软件,如果软件发布新版本则需要手动更新镜像。 + +而自动创建允许用户通过 Docker Hub 指定跟踪一个目标网站(目前支持 [GitHub](https://github.com/) 或 [BitBucket](https://bitbucket.org/))上的项目,一旦项目发生新的提交或者创建新的标签(tag),Docker Hub 会自动构建镜像并推送到 Docker Hub 中。 + +要配置自动创建,包括如下的步骤: + +- 创建并登录 Docker Hub,以及目标网站; +- 在目标网站中连接帐户到 Docker Hub; +- 在 Docker Hub 中 [配置一个自动创建](https://registry.hub.docker.com/builds/add/); +- 选取一个目标网站中的项目(需要含 `Dockerfile`)和分支; +- 指定 `Dockerfile` 的位置,并提交创建。 + +之后,可以在 Docker Hub 的 [自动创建页面](https://registry.hub.docker.com/builds/) 中跟踪每次创建的状态。 + diff --git a/docs/docker/containers-and-vm.png b/docs/docker/containers-and-vm.png deleted file mode 100644 index 38a10e3ebe53bac1979bc737ca0f8503d01ac0dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47867 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 Q0{EvSuPRq2^EU8*0a&>|+yDRo diff --git a/docs/docker/docker-cheat-sheet.md b/docs/docker/docker-cheat-sheet.md deleted file mode 100644 index f27b7dd..0000000 --- a/docs/docker/docker-cheat-sheet.md +++ /dev/null @@ -1,734 +0,0 @@ -# Docker Cheat Sheet - -> 本文转自 [Docker Cheat Sheet](https://github.com/wsargent/docker-cheat-sheet/blob/master/zh-cn/README.md) - -**想要一起来完善这份速查表吗?请看[贡献手册](#贡献手册contributing)部分!** - -目录 ---- - - - -- [为何使用 Docker](#为何使用-docker) -- [系统环境](#系统环境) -- [安装](#安装) -- [容器(Container)](#容器container) -- [镜像(Images)](#镜像images) -- [网络(Networks)](#网络networks) -- [仓管中心和仓库(Registry & Repository)](#仓管中心和仓库registry--repository) -- [Dockerfile](#dockerfile) -- [层(Layers)](#层layers) -- [链接(Links)](#链接links) -- [卷标(Volumes)](#卷标volumes) -- [暴露端口(Exposing ports)](#暴露端口exposing-ports) -- [最佳实践](#最佳实践) -- [安全(Security)](#安全security) -- [小贴士](#小贴士) -- [贡献手册(Contributing)](#贡献手册contributing) - - - -## 为何使用 Docker - -"通过 Docker, 开发者可以使用任何语言任何工具创建任何应用。“Dockerized” 的应用是完全可移植的,能在任何地方运行 - 不管是同事的 OS X 和 Windows 笔记本,或是在云端运行的 Ubuntu QA 服务,还是在虚拟机运行的 Red Hat 产品数据中心。 - -Docker Hub 上有 13,000+ 的应用,开发者可以从中选取一个进行快速扩展开发。Docker 跟踪管理变更和依赖关系,让系统管理员能更容易理解开发人员是如何让应用运转起来的。而开发者可以通过 Docker Hub 的共有/私有仓库,构建他们的自动化编译,与其他合作者共享成果。 - -Docker 帮助开发者更快地构建和发布高质量的应用。" -- [什么是 Docker](https://www.docker.com/what-docker/#copy1) - -## 系统环境 - -我用的是 [Oh My Zsh](https://github.com/robbyrussell/oh-my-zsh) ,和 [Docker 插件](https://github.com/robbyrussell/oh-my-zsh/wiki/Plugins#docker) ,它可以自动补全 docker 的命令。YMMV。 - -### Linux - -3.10.x 内核是能运行 Docker 的[最低要求](https://docs.docker.com/engine/installation/binaries/#check-kernel-dependencies)。 - -### MacOS - -10.8 “Mountain Lion” 或者更新的版本。 - -## 安装 - -### Linux - -Docker 提供了快速安装脚本: - -``` -curl -sSL https://get.docker.com/ | sh -``` - -如果你不想执行一个不明不白的 shell 脚本,那么请看[安装教程](https://docs.docker.com/engine/installation/linux/),选择你在用的发行版本。 - -如果你是一个 Docker 超新手,那么我建议你先去看看[系列教程](https://docs.docker.com/engine/getstarted/)。 - -### Mac OS X - -下载和安装 [Docker Toolbox](https://docs.docker.com/toolbox/overview/)。[Docker For Mac](https://docs.docker.com/docker-for-mac/) 很赞,但是它的安装和 VirtualBox 不太一样。详情请查阅[比较](https://docs.docker.com/docker-for-mac/docker-toolbox/)。 - -> **注意** 如果你已经有安装了 docker toolbox,那么你可能会考虑通过 [Docker Machine](https://docs.docker.com/machine/install-machine/) 安装包(不管是从 URL 或是 `docker-machine upgrade default`)升级,它确实会完成 docker-machine 的升级。但是它不会帮你升级 docker 版本 -- `docker-machine` 变成了 `1.10.3` 而 `docker` 还是原来的 `1.8.3` 或者你之前的什么版本。 -> -> 所以你最好是通过 Docker Toolbox DMG 文件来升级,它会一次性的帮你处理好所有的升级。 - -安装好 Docker Toolbox 之后,通过 VirtualBox provider 安装带 Docker Machine 的 VM: - -``` -docker-machine create --driver=virtualbox default -docker-machine ls -eval "$(docker-machine env default)" -``` - -然后启动 container: - -``` -docker run hello-world -``` - -好了,你现在有了一个运行中的 Docker container 了。 - -如果你是一个 Docker 超新手,那么我建议你先去看看[系列教程](https://docs.docker.com/engine/getstarted/)。 - -## 容器(Container) - -[最基本的 Docker 进程](http://etherealmind.com/basics-docker-containers-hypervisors-coreos/)。容器(Container)之于虚拟机(Virtual Machine)就好比线程之于进程。或者你可以把他们想成是 chroots on steroids。 - -### 生命周期 - -- [`docker create`](https://docs.docker.com/engine/reference/commandline/create) 创建一个容器但是不启动。 -- [`docker rename`](https://docs.docker.com/engine/reference/commandline/rename/) 允许重命名容器。 -- [`docker run`](https://docs.docker.com/engine/reference/commandline/run) 在同一个操作中创建并启动一个容器。 -- [`docker rm`](https://docs.docker.com/engine/reference/commandline/rm) 删除容器。 -- [`docker update`](https://docs.docker.com/engine/reference/commandline/update/) 更新容器的资源限制。 - -如果你想要一个临时容器,`docker run --rm` 会在容器停止之后删除它。 - -如果你想映射宿主(host)的一个文件夹到 docker 容器,`docker run -v $HOSTDIR:$DOCKERDIR`。参考 [Volumes](https://github.com/wsargent/docker-cheat-sheet/#volumes)。 - -如果你想同时删除和容器关联的 volumes ,那么在删除容器的时候必须包含 -v 选项,像这样 `docker rm -v`。 - -在 docker 1.10 中还有一个 [logging driver](https://docs.docker.com/engine/admin/logging/overview/),每个容器可以独立使用。如果你想执行 docker 并带上自定义日志驱动,这样 `docker run --log-driver=syslog` - -### 启动和停止 - -- [`docker start`](https://docs.docker.com/engine/reference/commandline/start) 启动容器。 -- [`docker stop`](https://docs.docker.com/engine/reference/commandline/stop) 停止运行中的容器。 -- [`docker restart`](https://docs.docker.com/engine/reference/commandline/restart) 停止之后再启动容器。 -- [`docker pause`](https://docs.docker.com/engine/reference/commandline/pause/) 暂停运行中的容器,将其 "冻结" 在当前状态。 -- [`docker unpause`](https://docs.docker.com/engine/reference/commandline/unpause/) 结束容器暂停状态。 -- [`docker wait`](https://docs.docker.com/engine/reference/commandline/wait) 阻塞,到运行中的容器停止为止。 -- [`docker kill`](https://docs.docker.com/engine/reference/commandline/kill) 向运行中容器发送 SIGKILL 指令。 -- [`docker attach`](https://docs.docker.com/engine/reference/commandline/attach) 链接到运行中容器。 - -如果你想整合容器到[宿主进程管理(host process manager)](https://docs.docker.com/engine/admin/host_integration/),那么以 `-r=false` 启动守护进程(daemon)然后使用 `docker start -a`。 - -如果你想通过宿主暴露容器的端口(ports),请看[暴露端口](#exposing-ports)一节。 - -故障 docker 实例的重启策略在[这里](http://container42.com/2014/09/30/docker-restart-policies/)。 - -#### CPU 限制 - -你可以限制 CPU,包括使用所有 CPU 的百分比,或者使用特定内核数。 - -比如,你可以设置 [`cpu-shares`](https://docs.docker.com/engine/reference/run/#/cpu-share-constraint) 。这个设置看起来有点奇怪 -- 1024 的意思是 100% CPU,因此如果你希望容器使用全体 CPU 内核的 50%,应将其设置为 512。更多信息,请查阅 https://goldmann.pl/blog/2014/09/11/resource-management-in-docker/#_cpu : - -``` -docker run -ti --c 512 agileek/cpuset-test -``` - -你可以只对某些 CPU 内核使用 [`cpuset-cpus`](https://docs.docker.com/engine/reference/run/#/cpuset-constraint)]。请参阅 https://agileek.github.io/docker/2014/08/06/docker-cpuset/ 获取更多细节以及一些不错的视频: - -``` -docker run -ti --cpuset-cpus=0,4,6 agileek/cpuset-test -``` - -注意,Docker 在容器内仍然可以**看到**所有的 CPU -- 虽然它只是用了其中一部分。请查阅 https://github.com/docker/docker/issues/20770 获取更多细节。 - -#### 内存限制 - -你同样可以在 Docker 设置[内存限制](https://docs.docker.com/engine/reference/run/#/user-memory-constraints) : - -``` -docker run -it -m 300M ubuntu:14.04 /bin/bash -``` - -#### 能力(Capabilities) - -Linux 的 capability 可以通过使用 `cap-add` 和 `cap-drop` 设置。请参阅 https://docs.docker.com/engine/reference/run/#/runtime-privilege-and-linux-capabilities 获取更多细节。这有助于提高安全性。 - -如需要挂载基于 FUSE 文件系统,你需要同时结合 --cap-add 和 --device 使用: - -``` -docker run --rm -it --cap-add SYS_ADMIN --device /dev/fuse sshfs -``` - -授予对单个设备访问权限: - -``` -docker run -it --device=/dev/ttyUSB0 debian bash -``` - -授予所有设备访问权限: - -``` -docker run -it --privileged -v /dev/bus/usb:/dev/bus/usb debian bash -``` - -有关容器特权的更多详情请参考[这里](https://docs.docker.com/engine/reference/run/#/runtime-privilege-and-linux-capabilities) - -### 信息 - -- [`docker ps`](https://docs.docker.com/engine/reference/commandline/ps) 查看运行中的所有容器。 -- [`docker logs`](https://docs.docker.com/engine/reference/commandline/logs) 从容器中获取日志。(你也可以使用自定义日志驱动,不过在 1.10 中,它只支持 `json-file` 和 `journald`) -- [`docker inspect`](https://docs.docker.com/engine/reference/commandline/inspect) 查看某个容器的所有信息(包括 IP 地址)。 -- [`docker events`](https://docs.docker.com/engine/reference/commandline/events) 从容器中获取事件(events)。 -- [`docker port`](https://docs.docker.com/engine/reference/commandline/port) 查看容器的公开端口。 -- [`docker top`](https://docs.docker.com/engine/reference/commandline/top) 查看容器中活动进程。 -- [`docker stats`](https://docs.docker.com/engine/reference/commandline/stats) 查看容器的资源使用情况统计信息。 -- [`docker diff`](https://docs.docker.com/engine/reference/commandline/diff) 查看容器的 FS 中有变化文件信息。 - -`docker ps -a` 查看所有容器,包括正在运行的和已停止的。 - -`docker stats --all` 显示正在运行的容器列表 - -### 导入 / 导出 - -- [`docker cp`](https://docs.docker.com/engine/reference/commandline/cp) 在容器和本地文件系统之间复制文件或文件夹。 -- [`docker export`](https://docs.docker.com/engine/reference/commandline/export) 将容器的文件系统切换为压缩包(tarball archive stream)输出到 STDOUT。 - -### 执行命令 - -- [`docker exec`](https://docs.docker.com/engine/reference/commandline/exec) 在容器中执行命令。 - -比如,进入正在运行的容器,在名为 foo 的容器中打开一个新的 shell 进程: `docker exec -it foo /bin/bash`. - -## 镜像(Images) - -镜像是[docker 容器的模板](https://docs.docker.com/engine/understanding-docker/#how-does-a-docker-image-work)。 - -### 生命周期 - -- [`docker images`](https://docs.docker.com/engine/reference/commandline/images) 查看所有镜像。 -- [`docker import`](https://docs.docker.com/engine/reference/commandline/import) 从压缩文件中创建镜像。 -- [`docker build`](https://docs.docker.com/engine/reference/commandline/build) 从 Dockerfile 创建镜像。 -- [`docker commit`](https://docs.docker.com/engine/reference/commandline/commit) 为容器创建镜像,如果容器正在运行则会临时暂停。 -- [`docker rmi`](https://docs.docker.com/engine/reference/commandline/rmi) 删除镜像。 -- [`docker load`](https://docs.docker.com/engine/reference/commandline/load) 通过 STDIN 从压缩包加载镜像,包括镜像和标签(images and tags) (0.7 起). -- [`docker save`](https://docs.docker.com/engine/reference/commandline/save) 通过 STDOUT 保存镜像到压缩包,包括所有的父层,标签和版本(parent layers, tags & versions) (0.7 起). - -### 信息 - -- [`docker history`](https://docs.docker.com/engine/reference/commandline/history) 查看镜像历史记录。 -- [`docker tag`](https://docs.docker.com/engine/reference/commandline/tag) 给镜像命名打标(tags) (本地或者仓库)。 - -### 清理 - -虽然你可以用 `docker rmi` 命令来删除指定的镜像,但是这里有个称为 [docker-gc](https://github.com/spotify/docker-gc) 的工具,它可以以一种安全的方式,清理掉那些不再被任何容器使用的镜像。 - -### 加载/保存镜像 - -从文件中加载镜像: - -``` -docker load < my_image.tar.gz -``` - -保存既有镜像: - -``` -docker save my_image:my_tag | gzip > my_image.tar.gz -``` - -### 导入/导出容器 - -从文件中将容器作为镜像导入: - -``` -cat my_container.tar.gz | docker import - my_image:my_tag -``` - -导出既有容器: - -``` -docker export my_container | gzip > my_container.tar.gz -``` - -### 加载被保存的镜像和导入作为镜像导出的容器之间的不同 - -通过 `load` 命令来加载镜像,会创建一个新的镜像,并继承原镜像的所有历史。 -通过 `import` 将容器作为镜像导入,也会创建一个新的镜像,但并不包含原镜像的历史,因此生成的镜像会比使用加载方式生成的镜像要小。 - -## 网络(Networks) - -Docker 有[网络(networks)](https://docs.docker.com/engine/userguide/networking/)功能。我并不是很了解它,所以这是一个扩展本文的好地方。这里有篇笔记指出,这是一种可以不使用端口来达成 docker 容器间通信的好方法。详情查阅[通过网络来工作](https://docs.docker.com/engine/userguide/networking/work-with-networks/)。 - -### 生命周期 - -- [`docker network create`](https://docs.docker.com/engine/reference/commandline/network_create/) -- [`docker network rm`](https://docs.docker.com/engine/reference/commandline/network_rm/) - -### 信息 - -- [`docker network ls`](https://docs.docker.com/engine/reference/commandline/network_ls/) -- [`docker network inspect`](https://docs.docker.com/engine/reference/commandline/network_inspect/) - -### 链接 - -- [`docker network connect`](https://docs.docker.com/engine/reference/commandline/network_connect/) -- [`docker network disconnect`](https://docs.docker.com/engine/reference/commandline/network_disconnect/) - -你可以为[容器指定 IP 地址](https://blog.jessfraz.com/post/ips-for-all-the-things/): - -``` -# 使用你自己的子网和网关创建一个桥接网络 -docker network create --subnet 203.0.113.0/24 --gateway 203.0.113.254 iptastic - -# 基于以上创建的网络,运行一个nginx容器并指定ip -$ docker run --rm -it --net iptastic --ip 203.0.113.2 nginx - -# 在其他地方使用curl访问这个ip(假设这是一个公网ip) -$ curl 203.0.113.2 -``` - -## 仓管中心和仓库(Registry & Repository) - -仓库(repository)是*被托管(hosted)*的已命名镜像(tagged images)集合,这组镜像用于构建容器文件系统。 - -仓管中心(registry)是一个*托管服务(host)* -- 一个服务,用于存储仓库和提供 HTTP API,以便[管理上传和下载仓库](https://docs.docker.com/engine/tutorials/dockerrepos/)。 - -Docker.com 把它自己的[索引](https://hub.docker.com/)托管到了它的仓管中心,那里有数量众多的仓库。不过话虽如此,这个仓管中心[并没有很好的验证镜像](https://titanous.com/posts/docker-insecurity),所以如果你很担心安全问题的话,请尽量避免使用它。 - -- [`docker login`](https://docs.docker.com/engine/reference/commandline/login) 登入仓管中心。 -- [`docker logout`](https://docs.docker.com/engine/reference/commandline/logout) 登出仓管中心。 -- [`docker search`](https://docs.docker.com/engine/reference/commandline/search) 从仓管中心检索镜像。 -- [`docker pull`](https://docs.docker.com/engine/reference/commandline/pull) 从仓管中心拉去镜像到本地。 -- [`docker push`](https://docs.docker.com/engine/reference/commandline/push) 从本地推送镜像到仓管中心。 - -### 本地仓管中心 - -你可以创立一个本地的仓管中心,通过使用 [docker distribution](https://github.com/docker/distribution) 工程,细节请查看 [本地发布(local deploy)](https://github.com/docker/docker.github.io/blob/master/registry/deploying.md) 介绍。 - -也可以参考 [邮件列表](https://groups.google.com/a/dockerproject.org/forum/#!forum/distribution)。 - -## Dockerfile - -[配置文件](https://docs.docker.com/engine/reference/builder/)。当你执行 `docker build` 的时候会根据该配置文件设置 Docker 容器。远优于使用 `docker commit`。 - -下面是一些常用的编写 Dockerfile 的编辑器和语法高亮模块︰ - -- 如果你使用 [jEdit](http://jedit.org),我为 [Dockerfile](https://github.com/wsargent/jedit-docker-mode) 做了个语法高亮模块。 -- [Sublime Text 2](https://packagecontrol.io/packages/Dockerfile%20Syntax%20Highlighting) -- [Atom](https://atom.io/packages/language-docker) -- [Vim](https://github.com/ekalinin/Dockerfile.vim) -- [Emacs](https://github.com/spotify/dockerfile-mode) -- [TextMate](https://github.com/docker/docker/tree/master/contrib/syntax/textmate) -- 如果要找更全面的关于编辑器或者 IDE 的内容,请看 [当 Docker 遇上 IDE](https://domeide.github.io/) - -### 指令 - -- [.dockerignore](https://docs.docker.com/engine/reference/builder/#dockerignore-file) -- [FROM](https://docs.docker.com/engine/reference/builder/#from) 为其他指令设置基础镜像(Base Image)。 -- [MAINTAINER](https://docs.docker.com/engine/reference/builder/#maintainer) 为生成的镜像设置作者字段。 -- [RUN](https://docs.docker.com/engine/reference/builder/#run) 在当前镜像的基础上生成一个新层并执行命令。 -- [CMD](https://docs.docker.com/engine/reference/builder/#cmd) 设置容器默认执行命令。 -- [EXPOSE](https://docs.docker.com/engine/reference/builder/#expose) 告知 Docker 容器在运行时所要监听的网络端口。注意:并没有实际上将端口设置为可访问。 -- [ENV](https://docs.docker.com/engine/reference/builder/#env) 设置环境变量。 -- [ADD](https://docs.docker.com/engine/reference/builder/#add) 将文件,文件夹或者远程文件复制到容器中。缓存无效。尽量用 `COPY` 代替 `ADD`。 -- [COPY](https://docs.docker.com/engine/reference/builder/#copy) 将文件或文件夹复制到容器中。 -- [ENTRYPOINT](https://docs.docker.com/engine/reference/builder/#entrypoint) 将一个容器设置为可执行。 -- [VOLUME](https://docs.docker.com/engine/reference/builder/#volume) 为外部挂载卷标或其他容器设置挂载点(mount point)。 -- [USER](https://docs.docker.com/engine/reference/builder/#user) 设置执行 RUN / CMD / ENTRYPOINT 命令的用户名。 -- [WORKDIR](https://docs.docker.com/engine/reference/builder/#workdir) 设置工作目录。 -- [ARG](https://docs.docker.com/engine/reference/builder/#arg) 定义编译时(build-time)变量。 -- [ONBUILD](https://docs.docker.com/engine/reference/builder/#onbuild) 添加触发指令,当该镜像被作为其他镜像的基础镜像时该指令会被触发。 -- [STOPSIGNAL](https://docs.docker.com/engine/reference/builder/#stopsignal) 设置通过系统向容器发出退出指令。 -- [LABEL](https://docs.docker.com/engine/userguide/labels-custom-metadata/) 将键值对元数据(key/value metadata)应用到你的镜像,容器,或者守护进程。 - -### 教程 - -- [Flux7's Dockerfile Tutorial](http://flux7.com/blogs/docker/docker-tutorial-series-part-3-automation-is-the-word-using-dockerfile/) - -### 例子 - -- [Examples](https://docs.docker.com/engine/reference/builder/#dockerfile-examples) -- [Best practices for writing Dockerfiles](https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/) -- [Michael Crosby](http://crosbymichael.com/) 还有更多的 [Dockerfiles best practices](http://crosbymichael.com/dockerfile-best-practices.html) / [take 2](http://crosbymichael.com/dockerfile-best-practices-take-2.html) -- [Building Good Docker Images](http://jonathan.bergknoff.com/journal/building-good-docker-images) / [Building Better Docker Images](http://jonathan.bergknoff.com/journal/building-better-docker-images) -- [Managing Container Configuration with Metadata](https://speakerdeck.com/garethr/managing-container-configuration-with-metadata) - -## 层(Layers) - -Docker 的版本化文件系统是基于层的。就像[git 的提交或文件变更系统](https://docs.docker.com/engine/userguide/storagedriver/imagesandcontainers/)一样。 - -注意: 如果你使用 [aufs](https://en.wikipedia.org/wiki/Aufs) 作为你的文件系统,当删除一个容器的时候,Docker 并不一定能成功删除的文件卷标!更多详细信息请参阅 [PR 8484](https://github.com/docker/docker/pull/8484)。 - -## 链接(Links) - -链接(Links)[通过 TCP/IP 端口](https://docs.docker.com/userguide/dockerlinks/)实现了 Docker 容器之间的通讯。[链接到 Redis](https://docs.docker.com/examples/running_redis_service/) 和 [Atlassian](https://blogs.atlassian.com/2013/11/docker-all-the-things-at-atlassian-automation-and-wiring/) 是两个可用的例子。你还可以[通过 hostname 关联链接](https://docs.docker.com/engine/userguide/networking/default_network/dockerlinks/#/updating-the-etchosts-file)。 - -注意: 如果你希望容器之间**只**通过链接进行通讯,在启动 docker 守护进程的时候请添加参数 `-icc=false` 来禁用内部进程通讯。 - -如果你有一个名为 CONTAINER 的容器(通过 `docker run --name CONTAINER` 指定) 并且在 Dockerfile 中,它的端口暴露为: - -``` -EXPOSE 1337 -``` - -然后,我们创建另外一个名为 LINKED 的容器: - -``` -docker run -d --link CONTAINER:ALIAS --name LINKED user/wordpress -``` - -然后 CONTAINER 的端口和别名将会以如下的环境变量出现在 LINKED 中: - -``` -$ALIAS_PORT_1337_TCP_PORT -$ALIAS_PORT_1337_TCP_ADDR -``` - -之后你就可以通过这种方式来链接它了。 - -要删除链接,通过命令 `docker rm --link`。 - -通常,docker 服务之间的链接,是"服务发现"的一个子集,如果你打算在生产中大规模使用 Docker,这将是一个很大的问题。请参阅[The Docker Ecosystem: Service Discovery and Distributed Configuration Stores](https://www.digitalocean.com/community/tutorials/the-docker-ecosystem-service-discovery-and-distributed-configuration-stores)获得更多细节。 - -## 卷标(Volumes) - -Docker 的卷标(volumes)是一个[free-floating 文件系统](https://docs.docker.com/engine/tutorials/dockervolumes/)。它们不应该链接到特定的容器上。好的做法是如果可能,应当把卷标挂载到[纯数据容器(data-only containers)](https://medium.com/@ramangupta/why-docker-data-containers-are-good-589b3c6c749e)上。 - -### 生命周期 - -- [`docker volume create`](https://docs.docker.com/engine/reference/commandline/volume_create/) -- [`docker volume rm`](https://docs.docker.com/engine/reference/commandline/volume_rm/) - -### 信息 - -- [`docker volume ls`](https://docs.docker.com/engine/reference/commandline/volume_ls/) -- [`docker volume inspect`](https://docs.docker.com/engine/reference/commandline/volume_inspect/) - -卷标在不能使用链接(只有 TCP/IP )的情况下非常有用。例如,如果你有两个 docker 实例需要通讯并在文件系统上留下记录。 - -你可以一次性将其挂载到多个 docker 容器上,通过 `docker run --volumes-from`。 - -因为卷标是独立的文件系统,它们通常被用于存储各容器之间的瞬时状态。也就是说,你可以配置一个无状态临时容器,关掉之后,当你有第二个这种临时容器实例的时候,你可以从上一次保存的状态继续执行。 - -查看[卷标进阶](http://crosbymichael.com/advanced-docker-volumes.html)来获取更多细节。Container42 [非常有用](http://container42.com/2014/11/03/docker-indepth-volumes/)。 - -你可以[将宿主 MacOS 的文件夹映射为 docker 卷标](https://docs.docker.com/engine/tutorials/dockervolumes/#mount-a-host-directory-as-a-data-volume): - -``` -docker run -v /Users/wsargent/myapp/src:/src -``` - -你也可以用远程 NFS 卷标,如果你觉得你[有足够勇气](https://docs.docker.com/engine/tutorials/dockervolumes/#/mount-a-shared-storage-volume-as-a-data-volume)。 - -可还可以考虑运行一个纯数据容器,像[这里](http://container42.com/2013/12/16/persistent-volumes-with-docker-container-as-volume-pattern/)所说的那样,提供可移植数据。 - -## 暴露端口(Exposing ports) - -通过宿主容器暴露输入端口是相当[繁琐,但有效](https://docs.docker.com/engine/reference/run/#expose-incoming-ports)的。 - -这种方式可以将容器端口映射到宿主端口上(只使用本地主机(localhost)接口),通过使用 `-p`: - -``` -docker run -p 127.0.0.1:$HOSTPORT:$CONTAINERPORT --name CONTAINER -t someimage -``` - -你可以告诉 Docker 容器在运行时监听指定的网络端口,通过使用 [EXPOSE](https://docs.docker.com/engine/reference/builder/#expose): - -``` -EXPOSE -``` - -但是注意 EXPOSE 并不会暴露端口,你需要用参数 `-p` 。比如说你要在 localhost 上暴露容器的端口: - -``` -iptables -t nat -A DOCKER -p tcp --dport -j DNAT --to-destination : -``` - -如果你是在 Virtualbox 中运行 Docker,那么你需要转发端口(forward the port),使用 [forwarded_port](https://docs.vagrantup.com/v2/networking/forwarded_ports.html)。它可以用于在 Vagrantfile 上配置暴露端口段,这样你就可以动态的映射它们了: - -``` -Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| - ... - - (49000..49900).each do |port| - config.vm.network :forwarded_port, :host => port, :guest => port - end - - ... -end -``` - -如果你忘记你将什么端口映射到宿主容器上的话,使用 `docker port` 来查看它: - -``` -docker port CONTAINER $CONTAINERPORT -``` - -## 最佳实践 - -这里有一些最佳实践的总结,以及一些讨论: - -- [The Rabbit Hole of Using Docker in Automated Tests](http://gregoryszorc.com/blog/2014/10/16/the-rabbit-hole-of-using-docker-in-automated-tests/) -- [Bridget Kromhout](https://twitter.com/bridgetkromhout) has a useful blog post on [running Docker in production](http://sysadvent.blogspot.co.uk/2014/12/day-1-docker-in-production-reality-not.html) at Dramafever. -- There's also a best practices [blog post](http://developers.lyst.com/devops/2014/12/08/docker/) from Lyst. -- [A Docker Dev Environment in 24 Hours!](https://engineering.salesforceiq.com/2013/11/05/a-docker-dev-environment-in-24-hours-part-2-of-2.html) -- [Building a Development Environment With Docker](https://tersesystems.com/2013/11/20/building-a-development-environment-with-docker/) -- [Discourse in a Docker Container](https://samsaffron.com/archive/2013/11/07/discourse-in-a-docker-container) - -## 安全(Security) - -这节准备讨论一些关于 Docker 安全性的问题。[安全](https://docs.docker.com/articles/security/)这章讲述了更多细节。 - -首先第一件事: Docker 是有 root 权限的。如果你在 `docker` 组,那么你就有[ root 权限](http://reventlov.com/advisories/using-the-docker-command-to-root-the-host)。如果你暴露了 docker unix socket 给容器,意味着你赋予了容器[宿主的 root 权限](https://www.lvh.io/posts/dont-expose-the-docker-socket-not-even-to-a-container.html)。Docker 不应该是你唯一的防御措施。 - -### 安全提示 - -为了最大的安全性,你应该会考虑在虚拟机上运行 Docker 。这是直接从 Docker 安全团队拿来的资料 -- [slides](http://www.slideshare.net/jpetazzo/linux-containers-lxc-docker-and-security) / [notes](http://www.projectatomic.io/blog/2014/08/is-it-safe-a-look-at-docker-and-security-from-linuxcon/)。然后,可以使用 AppArmor / seccomp / SELinux / grsec 之类的来[限制容器的权限](http://linux-audit.com/docker-security-best-practices-for-your-vessel-and-containers/)。更多细节,请查阅 [Docker 1.10 security features](https://blog.docker.com/2016/02/docker-engine-1-10-security/)。 - -Docker 镜像 id 属于[敏感信息](https://medium.com/@quayio/your-docker-image-ids-are-secrets-and-its-time-you-treated-them-that-way-f55e9f14c1a4) 所以它不应该向外界公开。你应该把他们当成密码来对待。 - -参考 [Docker Security Cheat Sheet](https://github.com/konstruktoid/Docker/blob/master/Security/CheatSheet.adoc)中 - 作者是 [Thomas Sjögren](https://github.com/konstruktoid) - 关于如何提高容器安全的建议。 - -下载[docker 安全测试脚本](https://github.com/docker/docker-bench-security),下载[白皮书](https://blog.docker.com/2015/05/understanding-docker-security-and-best-practices/) 以及订阅[邮件列表](https://www.docker.com/docker-security) (不幸的是 Docker 并没有独立的邮件列表,只有 dev / user)。 - -你应该远离那些使用编译版本 grsecurity / pax 的不稳定内核,比如 [Alpine Linux](https://en.wikipedia.org/wiki/Alpine_Linux)。如果在产品中用了 grsecurity ,那么你应该考虑使用有[商业支持](https://grsecurity.net/business_support.php)的[稳定版本](https://grsecurity.net/announce.php),就像你对待 RedHat 那样。它要 $200 每月,对于你的运维预算来说不值一提。 - -从 docker 1.11 开始,你可以轻松的限制在容器中可用的进程数,以防止 fork bombs。 这要求 linux 内核 >= 4.3 并且要在内核配置中打开 CGROUP_PIDS=y 。 - -``` -docker run --pids-limit=64 -``` - -同时,从 docker 1.11 开始,你也可以限制进程有再获取新权限的能力了。该功能是 linux 内核从 version 3.5 开始就拥有的。你可以从[这篇博客](http://www.projectatomic.io/blog/2016/03/no-new-privs-docker/)中阅读到更多关于这方面的内容。 - -``` -docker run --security-opt=no-new-privileges -``` - -参考 [Docker Security Cheat Sheet](http://container-solutions.com/content/uploads/2015/06/15.06.15_DockerCheatSheet_A2.pdf) (它是个 PDF 版本,搞得非常难用,所以拷贝出来了) 的 [容器解決方案](http://container-solutions.com/is-docker-safe-for-production/): - -关闭内部进程通讯: - -``` -docker -d --icc=false --iptables -``` - -设置容器为只读: - -``` -docker run --read-only -``` - -通过 hashsum 来验证卷标: - -``` -docker pull debian@sha256:a25306f3850e1bd44541976aa7b5fd0a29be -``` - -设置卷标为只读: - -``` -docker run -v $(pwd)/secrets:/secrets:ro debian -``` - -在 Dockerfile 中定义并运行一个用户,避免在容器中以 root 身份操作: - -``` -RUN groupadd -r user && useradd -r -g user user -USER user -``` - -### 用户命名空间(User Namespaces) - -还可以通过使用 [user namespaces](https://s3hh.wordpress.com/2013/07/19/creating-and-using-containers-without-privilege/) -- 这已经是 1.10 内建功能了,但默认情况下是不启用的。 - -要在 Ubuntu 15.10 中启用用户命名空间 ("remap the userns"),请[跟着这篇博客的例子](https://raesene.github.io/blog/2016/02/04/Docker-User-Namespaces/)来做。 - -### 安全相关视频 - -- [Using Docker Safely](https://youtu.be/04LOuMgNj9U) -- [Securing your applications using Docker](https://youtu.be/KmxOXmPhZbk) -- [Container security: Do containers actually contain?](https://youtu.be/a9lE9Urr6AQ) - -### 安全路线图 - -Docker 的路线图提到关于[seccomp 的支持](https://github.com/docker/docker/blob/master/ROADMAP.md#11-security)。 -这里有个 AppArmor 策略生成器,叫做 [bane](https://github.com/jfrazelle/bane),他们正在实现[安全配置文件](https://github.com/docker/docker/issues/17142)。 - -## 小贴士 - -来源: - -- [15 Docker Tips in 5 minutes](http://sssslide.com/speakerdeck.com/bmorearty/15-docker-tips-in-5-minutes) - -### 最后的 Ids - -``` -alias dl='docker ps -l -q' -docker run ubuntu echo hello world -docker commit `dl` helloworld -``` - -### 带命令行的提交 (需要 Dockerfile) - -``` -docker commit -run='{"Cmd":["postgres", "-too -many -opts"]}' `dl` postgres -``` - -### 获取 IP 地址 - -``` -docker inspect `dl` | grep IPAddress | cut -d '"' -f 4 -``` - -或者安装 [jq](https://stedolan.github.io/jq/): - -``` -docker inspect `dl` | jq -r '.[0].NetworkSettings.IPAddress' -``` - -或者用[go 模板](https://docs.docker.com/engine/reference/commandline/inspect) - -``` -docker inspect -f '{{ .NetworkSettings.IPAddress }}' -``` - -### 获取端口映射 - -``` -docker inspect -f '{{range $p, $conf := .NetworkSettings.Ports}} {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' -``` - -### 通过正则获取容器 - -``` -for i in $(docker ps -a | grep "REGEXP_PATTERN" | cut -f1 -d" "); do echo $i; done` -``` - -### 获取环境设定 - -``` -docker run --rm ubuntu env -``` - -### 强迫关闭正在运行的容器 - -``` -docker kill $(docker ps -q) -``` - -### 删除旧容器 - -``` -docker ps -a | grep 'weeks ago' | awk '{print $1}' | xargs docker rm -``` - -### 删除停止容器 - -``` -docker rm -v `docker ps -a -q -f status=exited` -``` - -### 删除 dangling 镜像 - -``` -docker rmi $(docker images -q -f dangling=true) -``` - -### 删除所有镜像 - -``` -docker rmi $(docker images -q) -``` - -### 删除 dangling 卷标 - -Docker 1.9 开始: - -``` -docker volume rm $(docker volume ls -q -f dangling=true) -``` - -1.9.0 中,过滤器 `dangling=false` 居然 _没_ 用 - 它会被忽略然后列出所有的卷标。 - -### 查看镜像依赖 - -``` -docker images -viz | dot -Tpng -o docker.png -``` - -### Docker 容器瘦身 [Intercity 博客](http://bit.ly/1Wwo61N) - -- 在当前运行层(RUN layer)清理 APT - -这应当和其他 apt 命令在同一层中完成。 -否则,前面的层将会保持原有信息,而你的镜像则依旧臃肿。 - -``` -RUN {apt commands} \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -``` - -- 压缩镜像 - -``` -ID=$(docker run -d image-name /bin/bash) -docker export $ID | docker import – flat-image-name -``` - -- 备份 - -``` -ID=$(docker run -d image-name /bin/bash) -(docker export $ID | gzip -c > image.tgz) -gzip -dc image.tgz | docker import - flat-image-name -``` - -### 监视运行中容器的系统资源利用率 - -检查某个单独容器的 CPU, 内存, 和 网络 i/o 使用情况,你可以: - -``` -docker stats -``` - -按 id 列出所有的容器: - -``` -docker stats $(docker ps -q) -``` - -按名称列出所有容器: - -``` -docker stats $(docker ps --format '{{.Names}}') -``` - -按指定镜像名称列出所有容器: - -``` -docker ps -a -f ancestor=ubuntu -``` - -## 贡献手册(Contributing) - -这是关于如何为这份速查表做贡献的说明。 - -### 打开 README.md - -点击 [README.md](https://github.com/wsargent/docker-cheat-sheet/blob/master/README.md) <-- 这个链接 - -![点击它](../images/click.png) - -### 编辑页面 - -![选择它](../images/edit.png) - -### 更新和提交 - -![改变它](../images/change.png) - -![提交](../images/commit.png) diff --git a/docs/docker/docker-introduction.md b/docs/docker/docker-introduction.md new file mode 100644 index 0000000..0ba12de --- /dev/null +++ b/docs/docker/docker-introduction.md @@ -0,0 +1,95 @@ +# Docker 简介 + +> **Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。** + +## 概述 + +### Docker 是什么 + +Docker 是开发,传输和运行应用程序的开放平台。 + +Docker 使您能够将应用程序与基础架构分开,以便快速交付软件。 + +借助 Docker,您可以像管理应用程序一样管理基础架构。通过利用 Docker 的方法快速进行运输,测试和部署代码,您可以显著缩短编写代码和在生产环境中运行代码之间的耗时。 + +### Docker 平台 + +Docker 提供了被称为容器的松散隔离环境,在环境中可以打包和运行应用程序。隔离和安全性允许您在给定主机上同时运行多个容器。容器是轻量级的,因为它们不需要管理程序的额外负载,而是直接在主机的内核中运行。这意味着您可以在给定的硬件组合上运行更多容器,而不是使用虚拟机。你甚至可以在实际上是虚拟机的主机中运行 Docker 容器! + +Docker 提供工具和平台来管理容器的生命周期: + +- 使用容器开发您的应用程序及其支持组件。 +- 容器成为分发和测试你的应用程序的单元。 +- 准备好后,将您的应用程序部署到生产环境中,作为容器或协调服务。无论您的生产环境是本地数据中心,云提供商还是两者的混合,这都是一样的。 + +### Docker 引擎 + +Docker 引擎是一个 C/S 架构的应用,它有这些主要的组件: + +服务器是一个长期运行的程序,被称为守护进程。 + +REST API 指定程序可用于与守护进程进行通信并指示其执行操作的接口。 + +命令行客户端。 + +![https://docs.docker.com/engine/images/engine-components-flow.png](https://docs.docker.com/engine/images/engine-components-flow.png) + +CLI 使用 Docker REST API 通过脚本或直接 CLI 命令来控制 Docker 守护进程或与其进行交互。许多其他 Docker 应用程序使用底层的 API 和 CLI。 + +守护进程创建并管理 Docker 对象,如镜像,容器,网络和卷。 + +### 传统虚拟机和 Docker + +![](http://dunwu.test.upcdn.net/images/os/docker/containers-and-vm.png) + +## 概念 + +Docker 包括三个基本概念 + +- 镜像(Image) +- 容器(Container) +- 仓库(Repository) + +### 镜像 + +我们都知道,操作系统分为内核和用户空间。对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而 Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:18.04 就包含了完整的一套 Ubuntu 18.04 最小系统的 root 文件系统。 + +Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。 + +#### 分层存储 + +因为镜像包含操作系统完整的 root 文件系统,其体积往往是庞大的,因此在 Docker 设计时,就充分利用 Union FS 的技术,将其设计为分层存储的架构。所以严格来说,镜像并非是像一个 ISO 那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。 + +镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。 + +分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。 + +### 容器 + +镜像(`Image`)和容器(`Container`)的关系,就像是面向对象程序设计中的 `类` 和 `实例` 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。 + +容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 [命名空间](https://en.wikipedia.org/wiki/Linux_namespaces)。因此容器可以拥有自己的 `root` 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。也因为这种隔离的特性,很多人初学 Docker 时常常会混淆容器和虚拟机。 + +前面讲过镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为**容器存储层**。 + +容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。 + +按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用 [数据卷(Volume)](https://yeasy.gitbooks.io/docker_practice/content/data_management/volume.html)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。 + +数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失。 + +### 仓库 + +镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,[Docker Registry](https://yeasy.gitbooks.io/docker_practice/content/repository/registry.html) 就是这样的服务。 + +一个 **Docker Registry** 中可以包含多个**仓库**(`Repository`);每个仓库可以包含多个**标签**(`Tag`);每个标签对应一个镜像。 + +通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 `<仓库名>:<标签>` 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 `latest` 作为默认标签。 + +以 [Ubuntu 镜像](https://store.docker.com/images/ubuntu) 为例,`ubuntu` 是仓库的名字,其内包含有不同的版本标签,如,`16.04`, `18.04`。我们可以通过 `ubuntu:14.04`,或者 `ubuntu:18.04` 来具体指定所需哪个版本的镜像。如果忽略了标签,比如 `ubuntu`,那将视为 `ubuntu:latest`。 + +仓库名经常以 _两段式路径_ 形式出现,比如 `jwilder/nginx-proxy`,前者往往意味着 Docker Registry 多用户环境下的用户名,后者则往往是对应的软件名。但这并非绝对,取决于所使用的具体 Docker Registry 的软件或服务。 + +## 引用和引申 + +- https://yeasy.gitbooks.io/docker_practice/content/basic_concept/repository.html diff --git a/docs/docker/docker-quickstart.md b/docs/docker/docker-quickstart.md index 888731e..e88355c 100644 --- a/docs/docker/docker-quickstart.md +++ b/docs/docker/docker-quickstart.md @@ -1,124 +1,43 @@ -# Docker +# Docker 入门 - + -- [镜像(Images)](#镜像images) -- [容器(Container)](#容器container) -- [网络(Networks)](#网络networks) -- [仓管中心和仓库(Registry & Repository)](#仓管中心和仓库registry--repository) -- [Dockerfile](#dockerfile) -- [卷标(Volumes)](#卷标volumes) +- [Hello World 示例](#hello-world-示例) -## 镜像(Images) +## Hello World 示例 -- [`docker image ls`](https://github.com/yeasy/docker_practice/blob/master/image/list.md) - 查看所有镜像。 -- [`docker image rm`](https://github.com/yeasy/docker_practice/blob/master/image/rm.md) - 删除本地镜像。 -- `docker import` - 从压缩文件中创建镜像。 -- `docker export` - 导出既有容器。 -- `docker build` - 从 Dockerfile 创建镜像。 -- `docker commit` - 为容器创建镜像,如果容器正在运行则会临时暂停。 -- `docker rmi` - 删除镜像。 -- `docker load` - 通过 STDIN 从压缩包加载镜像,包括镜像和标签(images and tags) (0.7 起). -- `docker save` - 通过 STDOUT 保存镜像到压缩包,包括所有的父层,标签和版本(parent layers, tags & versions) (0.7 起). -- `docker history` - 查看镜像历史记录。 -- `docker tag` - 给镜像命名打标(tags) (本地或者仓库)。 +(1)拉取镜像 -## 容器(Container) +``` +docker image pull library/hello-world +``` -### 生命周期 +`docker image pull` 是抓取 image 文件的命令。`library/hello-world` 是 image 文件在仓库里面的位置,其中 `library` 是 image 文件所在的组,`hello-world` 是 image 文件的名字。 -- `docker create` - 创建一个容器但是不启动。 -- `docker rename` - 允许重命名容器。 -- `docker run` - 在同一个操作中创建并启动一个容器。 -- `docker rm` - 删除容器。 -- `docker update` - 更新容器的资源限制。 +由于 Docker 官方提供的 image 文件,都放在[`library`](https://hub.docker.com/r/library/)组里面,所以它的是默认组,可以省略。因此,上面的命令可以写成下面这样。 -### 启动和停止 +``` +docker image pull hello-world +``` -- `docker start` - 启动容器。 -- `docker stop` - 停止运行中的容器。 -- `docker restart` - 停止之后再启动容器。 -- `docker pause` - 暂停运行中的容器,将其 "冻结" 在当前状态。 -- `docker unpause` - 结束容器暂停状态。 -- `docker wait` - 阻塞,到运行中的容器停止为止。 -- `docker kill` - 向运行中容器发送 SIGKILL 指令。 -- `docker attach` - 链接到运行中容器。 +(2)查看镜像 -### 信息 +```sh +~ docker image ls +REPOSITORY TAG IMAGE ID CREATED SIZE +hello-world latest 4ab4c602aa5e 3 months ago 1.84kB +``` -- `docker ps` - 查看运行中的所有容器。 -- `docker logs` - 从容器中获取日志。(你也可以使用自定义日志驱动,不过在 1.10 中,它只支持 json-file 和 journald) -- `docker inspect` - 查看某个容器的所有信息(包括 IP 地址)。 -- `docker events` - 从容器中获取事件(events)。 -- `docker port` - 查看容器的公开端口。 -- `docker top` - 查看容器中活动进程。 -- `docker stats` - 查看容器的资源使用情况统计信息。 -- `docker diff` - 查看容器的 FS 中有变化文件信息。 +(3)运行镜像 -### 导入 / 导出 +``` +docker container run hello-world +``` -docker cp 在容器和本地文件系统之间复制文件或文件夹。 -docker export 将容器的文件系统切换为压缩包(tarball archive stream)输出到 STDOUT。 +`docker container run` 命令会从 image 文件,生成一个正在运行的容器实例。 -### 执行命令 +注意,`docker container run` 命令具有自动抓取 image 文件的功能。如果发现本地没有指定的 image 文件,就会从仓库自动抓取。因此,前面的 `docker image pull` 命令并不是必需的步骤。 -docker exec 在容器中执行命令。 - -## 网络(Networks) - -### 生命周期 - -- `docker network create` -- `docker network rm` - -### 信息 - -- `docker network ls` -- `docker network inspect` - -### 链接 - -- `docker network connect` -- `docker network disconnect` - -## 仓管中心和仓库(Registry & Repository) - -- `docker login` - 登入仓管中心。 -- `docker logout` - 登出仓管中心。 -- `docker search` - 从仓管中心检索镜像。 -- `docker pull` - 从仓管中心拉去镜像到本地。 -- `docker push` - 从本地推送镜像到仓管中心。 - -## Dockerfile - -- .dockerignore -- FROM 为其他指令设置基础镜像(Base Image)。 -- MAINTAINER 为生成的镜像设置作者字段。 -- RUN 在当前镜像的基础上生成一个新层并执行命令。 -- CMD 设置容器默认执行命令。 -- EXPOSE 告知 Docker 容器在运行时所要监听的网络端口。注意:并没有实际上将端口设置为可访问。 -- ENV 设置环境变量。 -- ADD 将文件,文件夹或者远程文件复制到容器中。缓存无效。尽量用 COPY 代替 ADD。 -- COPY 将文件或文件夹复制到容器中。 -- ENTRYPOINT 将一个容器设置为可执行。 -- VOLUME 为外部挂载卷标或其他容器设置挂载点(mount point)。 -- USER 设置执行 RUN / CMD / ENTRYPOINT 命令的用户名。 -- WORKDIR 设置工作目录。 -- ARG 定义编译时(build-time)变量。 -- ONBUILD 添加触发指令,当该镜像被作为其他镜像的基础镜像时该指令会被触发。 -- STOPSIGNAL 设置通过系统向容器发出退出指令。 -- LABEL 将键值对元数据(key/value metadata)应用到你的镜像,容器,或者守护进程。 - -## 卷标(Volumes) - -### 生命周期 - -- `docker volume create` -- `docker volume rm` - -### 信息 - -- `docker volume ls` -- `docker volume inspect` +如果运行成功,你会在屏幕上读到下面的输出。 diff --git a/docs/docker/docker.md b/docs/docker/docker.md new file mode 100644 index 0000000..a115151 --- /dev/null +++ b/docs/docker/docker.md @@ -0,0 +1,70 @@ +# Docker 教程 + +## [简介](docker-introduction.md) + +> **Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。** +> +> Docker 将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了 Docker,就不用担心环境问题。 + +![](http://dunwu.test.upcdn.net/images/os/docker/containers-and-vm.png) + +## [入门篇](docker-quickstart.md) + +## 基础篇 + +### 安装 + +Docker 分为 CE 和 EE 两大版本。 + +- CE 即社区版(免费,支持周期 7 个月)。Docker CE 分为 `stable`, `test`, 和 `nightly` 三个更新频道。每六个月发布一个 stable 版本。 +- EE 即企业版,强调安全,付费使用,支持周期 24 个月。 + +Docker CE 可以安装在 Linux 、Windows 10 (PC) 和 MAC 上。 + +> 参考: +> +> - [官方安装指南](https://docs.docker.com/install/) +> - [Docker 中文教程安装指南](https://yeasy.gitbooks.io/docker_practice/content/install/) + +### [Docker 镜像](basics/docker-image.md) + +### [Docker 容器](basics/docker-container.md) + +### [Dockerfile](basics/docker-dockerfile.md) + +- FROM(指定基础镜像) +- RUN(执行命令) +- COPY(复制文件) +- ADD(更高级的复制文件) +- CMD(容器启动命令) +- ENTRYPOINT(入口点) +- ENV(设置环境变量) +- ARG(构建参数) +- VOLUME(定义匿名卷) +- EXPOSE(暴露端口) +- WORKDIR(指定工作目录) +- USER(指定当前用户) +- HEALTHCHECK(健康检查) +- ONBUILD(为他人作嫁衣裳) + +## 进阶篇 + +### 设计 + +## 实战篇 + +## 常见问题 + +## 附录 + +### [命令](appendix/docker-cli.md) + +### [资源](appendix/docker-resources.md) + +### 术语 + +### 技巧 + +### 版本 + +### 反馈 diff --git a/docs/kubernetes/kubernetes.md b/docs/kubernetes/kubernetes.md new file mode 100644 index 0000000..283fcb2 --- /dev/null +++ b/docs/kubernetes/kubernetes.md @@ -0,0 +1,176 @@ +# Kubernetes + +> Kubernetes 是用于自动部署,扩展和管理 Docker 应用程序的开源系统。简称 K8S +> +> 关键词: `docker` + + + +- [功能](#功能) +- [简介](#简介) +- [基础](#基础) +- [进阶](#进阶) +- [命令](#命令) +- [引用和引申](#引用和引申) + + + +## 功能 + +- 基于容器的应用部署、维护和滚动升级 +- 负载均衡和服务发现 +- 跨机器和跨地区的集群调度 +- 自动伸缩 +- 无状态服务和有状态服务 +- 广泛的 Volume 支持 +- 插件机制保证扩展性 + +## 简介 + +Kubernetes 主控组件(Master) 包含三个进程,都运行在集群中的某个节上,通常这个节点被称为 master 节点。这些进程包括:`kube-apiserver`、`kube-controller-manager` 和 `kube-scheduler`。 + +集群中的每个非 master 节点都运行两个进程: + +- kubelet,和 master 节点进行通信。 +- kube-proxy,一种网络代理,将 Kubernetes 的网络服务代理到每个节点上。 + +### Kubernetes 对象 + +Kubernetes 包含若干抽象用来表示系统状态,包括:已部署的容器化应用和负载、与它们相关的网络和磁盘资源以及有关集群正在运行的其他操作的信息。 + + +- Pod - kubernetes 对象模型中最小的单元,它代表集群中一个正在运行的进程。 +- Service +- Volume +- Namespace + +![](http://dunwu.test.upcdn.net/images/os/kubernetes/pod.svg) + +高级对象 + +- ReplicaSet +- Deployment +- StatefulSet +- DaemonSet +- Job + +## 基础 + +## 进阶 + +## 命令 + +### 客户端配置 + +```sh +# Setup autocomplete in bash; bash-completion package should be installed first +source <(kubectl completion bash) + +# View Kubernetes config +kubectl config view + +# View specific config items by json path +kubectl config view -o jsonpath='{.users[?(@.name == "k8s")].user.password}' + +# Set credentials for foo.kuberntes.com +kubectl config set-credentials kubeuser/foo.kubernetes.com --username=kubeuser --password=kubepassword +``` + +### 查找资源 + +```sh +# List all services in the namespace +kubectl get services + +# List all pods in all namespaces in wide format +kubectl get pods -o wide --all-namespaces + +# List all pods in json (or yaml) format +kubectl get pods -o json + +# Describe resource details (node, pod, svc) +kubectl describe nodes my-node + +# List services sorted by name +kubectl get services --sort-by=.metadata.name + +# List pods sorted by restart count +kubectl get pods --sort-by='.status.containerStatuses[0].restartCount' + +# Rolling update pods for frontend-v1 +kubectl rolling-update frontend-v1 -f frontend-v2.json + +# Scale a replicaset named 'foo' to 3 +kubectl scale --replicas=3 rs/foo + +# Scale a resource specified in "foo.yaml" to 3 +kubectl scale --replicas=3 -f foo.yaml + +# Execute a command in every pod / replica +for i in 0 1; do kubectl exec foo-$i -- sh -c 'echo $(hostname) > /usr/share/nginx/html/index.html'; done +``` + +### 资源管理 + +```sh +# Get documentation for pod or service +kubectl explain pods,svc + +# Create resource(s) like pods, services or daemonsets +kubectl create -f ./my-manifest.yaml + +# Apply a configuration to a resource +kubectl apply -f ./my-manifest.yaml + +# Start a single instance of Nginx +kubectl run nginx --image=nginx + +# Create a secret with several keys +cat < 什么是 Linux? +> +> Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 UNIX 的多用户、多任务、支持多线程和多 CPU 的操作系统。它能运行主要的 UNIX 工具软件、应用程序和网络协议。它支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。 + +> 为什么学习 Linux? +> +> Linux 常用于网站服务器或嵌入式应用。世界上大部分网站都部署在 Linux 服务器上,作为一名 web 开发人员, + +如何学习 Linux? + +## :memo: 知识点 + +### [Linux 命令](cli) + +> 根据应用场景,将常见 Linux 命令分门别类的一一介绍。 +> +> 如果想快速学习,推荐参考这篇文章:[命令行的艺术(转载)](cli/命令行的艺术.md) + +1. [查看 Linux 命令帮助信息](cli/01.查看Linux命令帮助信息.md) - 关键词:`help`, `whatis`, `info`, `which`, `whereis`, `man` +2. [Linux 文件目录管理](cli/02.Linux文件目录管理.md) - 关键词:`cd`, `ls`, `pwd`, `mkdir`, `rmdir`, `tree`, `touch`, `ln`, `rename`, `stat`, `file`, `chmod`, `chown`, `locate`, `find`, `cp`, `mv`, `rm` +3. [Linux 文件内容查看命令](cli/03.Linux文件内容查看编辑.md) - 关键词:`cat`, `head`, `tail`, `more`, `less`, `sed`, `vi`, `grep` +4. [Linux 文件压缩和解压](cli/04.Linux文件压缩和解压.md) - 关键词:`tar`, `gzip`, `zip`, `unzip` +5. [Linux 用户管理](cli/05.Linux用户管理.md) - 关键词:`groupadd`, `groupdel`, `groupmod`, `useradd`, `userdel`, `usermod`, `passwd`, `su`, `sudo` +6. [Linux 系统管理](cli/06.Linux系统管理.md) - 关键词:`reboot`, `exit`, `shutdown`, `date`, `mount`, `umount`, `ps`, `kill`, `systemctl`, `service`, `crontab` +7. [Linux 网络管理](cli/07.Linux网络管理.md) - 关键词:关键词:`curl`, `wget`, `telnet`, `ip`, `hostname`, `ifconfig`, `route`, `ssh`, `ssh-keygen`, `firewalld`, `iptables`, `host`, `nslookup`, `nc`/`netcat`, `ping`, `traceroute`, `netstat` +8. [Linux 硬件管理](cli/08.Linux硬件管理.md) - 关键词:`df`, `du`, `top`, `free`, `iotop` +9. [Linux 软件管理](cli/09.Linux软件管理.md) - 关键词:`rpm`, `yum`, `apt-get` + +### [工具](tool) + +- [Git](tool/git) +- [Vim](tool/vim.md) + +### [Linux 运维](ops) + +#### Linux 服务器运维 + +- [Linux 典型运维应用](ops/linux典型运维应用.md) +- [samba 使用详解](ops/samba使用详解.md) + +#### 应用、服务、工具运维和调优 + +- 研发环境 + - [JDK](ops/service/jdk.md) + - [Nodejs](ops/service/nodejs.md) + - [Tomcat](ops/service/tomcat.md) + - [Zookeeper](ops/service/zookeeper.md) +- 研发工具 + - [Nexus](ops/service/nexus.md) + - [Jenkins](ops/service/jenkins.md) - 持续集成和持续交付平台。 + - [Elastic](ops/service/elastic) - 常被称为 `ELK` ,是 Java 世界最流行的分布式日志解决方案 。 `ELK` 是 Elastic 公司旗下三款产品 [ElasticSearch](https://www.elastic.co/products/elasticsearch) 、[Logstash](https://www.elastic.co/products/logstash) 、[Kibana](https://www.elastic.co/products/kibana) 的首字母组合。 + - [Apollo](ops/service/apollo) - 分布式配置中心 +- 版本控制 + - [Gitlab](ops/service/gitlab) - Git 代码管理平台。 + - [Svn](ops/service/svn.md) - Svn 是 Subversion 的简称,是一个开放源代码的版本控制系统,它采用了分支管理系统。 +- 消息中间件 + - [Kafka](ops/service/kafka.md) - 应该是 Java 世界最流行的消息中间件了吧。 + - [RocketMQ](ops/service/rocketmq.md) - 阿里巴巴开源的消息中间件。 +- 数据库 + - [Mysql](https://github.com/dunwu/database/blob/master/docs/mysql/install-mysql.md) - 关系型数据库 + - [PostgreSQL](https://github.com/dunwu/database/blob/master/docs/postgresql.md#安装) - 关系型数据库 + - [Mongodb](https://github.com/dunwu/database/blob/master/docs/mongodb/install-mongodb.md) - Nosql + - [Redis](https://github.com/dunwu/database/blob/master/docs/redis/install-redis.md) - Nosql + +## :books: 学习资源 ### Linux 资源汇总 @@ -33,3 +87,7 @@ - [Linux 命令大全](http://man.linuxde.net/) - Linux 命令在线帮助手册 - [linux-command](https://github.com/jaywcjlove/linux-command) - Linux 命令在线帮助手册 - [linux-tutorial](https://github.com/judasn/Linux-Tutorial) - Linux 环境下各种软件安装部署 + +## :door: 传送门 + +| [回首頁](https://github.com/dunwu/os-tutorial) | diff --git a/docs/linux/01.查看Linux命令帮助信息.md b/docs/linux/cli/01.查看Linux命令帮助信息.md similarity index 100% rename from docs/linux/01.查看Linux命令帮助信息.md rename to docs/linux/cli/01.查看Linux命令帮助信息.md diff --git a/docs/linux/02.Linux文件目录管理.md b/docs/linux/cli/02.Linux文件目录管理.md similarity index 92% rename from docs/linux/02.Linux文件目录管理.md rename to docs/linux/cli/02.Linux文件目录管理.md index c46e9e4..78ad1f6 100644 --- a/docs/linux/02.Linux文件目录管理.md +++ b/docs/linux/cli/02.Linux文件目录管理.md @@ -10,7 +10,7 @@ tags: # Linux 文件目录管理 -> 关键词:`cd`, `ls`, `pwd`, `mkdir`, `rmdir`, `tree`, `touch`, `ln`, `rename`, `stat`, `file`, `chmod`, `chown`, `locate`, `find`, `cp`, `mv`, `rm` +> 关键词:`cd`, `ls`, `pwd`, `mkdir`, `rmdir`, `tree`, `touch`, `ln`, `rename`, `stat`, `file`, `chmod`, `chown`, `locate`, `find`, `cp`, `scp`, `mv`, `rm` @@ -38,6 +38,7 @@ tags: - [locate](#locate) - [find](#find) - [cp](#cp) + - [scp](#scp) - [mv](#mv) - [rm](#rm) @@ -131,6 +132,7 @@ dr-xr-xr-x 4 root root 4096 Apr 19 2012 boot ### 文件和目录通用管理 - 复制文件或目录 - 使用 [cp](#cp) +- 复制文件或目录到远程服务器 - 使用 [scp](#scp) - 移动文件或目录 - 使用 [mv](#mv) - 删除文件或目录 - 使用 [rm](#rm) @@ -440,6 +442,41 @@ cp -rf /usr/men/* /usr/zh cp -i /usr/men m*.c /usr/zh ``` +### scp + +> scp 命令用于在 Linux 下进行远程拷贝文件的命令,和它类似的命令有 cp,不过 cp 只是在本机进行拷贝不能跨服务器,而且 scp 传输是加密的。可能会稍微影响一下速度。当你服务器硬盘变为只读 read only system 时,用 scp 可以帮你把文件移出来。另外,scp 还非常不占资源,不会提高多少系统负荷,在这一点上,rsync 就远远不及它了。虽然 rsync 比 scp 会快一点,但当小文件众多的情况下,rsync 会导致硬盘 I/O 非常高,而 scp 基本不影响系统正常使用。 + +示例: + +```bash +# 拷贝文件到远程服务器的指定目录 +scp @: +scp test.txt root@192.168.0.1:/opt + +# 拷贝目录到远程服务器的指定目录 +scp -r @: +scp -r test root@192.168.0.1:/opt +``` + +#### 免密码传输 + +(1)生成 ssh 公私钥对 + +``` +ssh-keygen -t rsa +``` + +(2)将服务器 A 的 `~/.ssh/id_rsa.pub` 文件内容复制到服务器 B 的 `~/.ssh/authorized_keys` 文件中。 + +```bash +# 服务器 A 上执行以下命令 +scp ~/.ssh/id_rsa.pub root@192.168.0.2:~/.ssh/id_rsa.pub.tmp + +# 服务器 B 上执行以下命令 +cat ~/.ssh/id_rsa.pub.tmp >> ~/.ssh/authorized_keys +rm ~/.ssh/id_rsa.pub.tmp +``` + ### mv > mv 命令用来对文件或目录重新命名,或者将文件从一个目录移到另一个目录中。source 表示源文件或目录,target 表示目标文件或目录。如果将一个文件移到一个已经存在的目标文件中,则目标文件的内容将被覆盖。 diff --git a/docs/linux/03.Linux文件内容查看编辑.md b/docs/linux/cli/03.Linux文件内容查看编辑.md similarity index 100% rename from docs/linux/03.Linux文件内容查看编辑.md rename to docs/linux/cli/03.Linux文件内容查看编辑.md diff --git a/docs/linux/04.Linux文件压缩和解压.md b/docs/linux/cli/04.Linux文件压缩和解压.md similarity index 100% rename from docs/linux/04.Linux文件压缩和解压.md rename to docs/linux/cli/04.Linux文件压缩和解压.md diff --git a/docs/linux/05.Linux用户管理.md b/docs/linux/cli/05.Linux用户管理.md similarity index 100% rename from docs/linux/05.Linux用户管理.md rename to docs/linux/cli/05.Linux用户管理.md diff --git a/docs/linux/06.Linux系统管理.md b/docs/linux/cli/06.Linux系统管理.md similarity index 100% rename from docs/linux/06.Linux系统管理.md rename to docs/linux/cli/06.Linux系统管理.md diff --git a/docs/linux/07.Linux网络管理.md b/docs/linux/cli/07.Linux网络管理.md similarity index 100% rename from docs/linux/07.Linux网络管理.md rename to docs/linux/cli/07.Linux网络管理.md diff --git a/docs/linux/08.Linux硬件管理.md b/docs/linux/cli/08.Linux硬件管理.md similarity index 100% rename from docs/linux/08.Linux硬件管理.md rename to docs/linux/cli/08.Linux硬件管理.md diff --git a/docs/linux/09.Linux软件管理.md b/docs/linux/cli/09.Linux软件管理.md similarity index 100% rename from docs/linux/09.Linux软件管理.md rename to docs/linux/cli/09.Linux软件管理.md diff --git a/docs/linux/cli/README.md b/docs/linux/cli/README.md new file mode 100644 index 0000000..7f4ba06 --- /dev/null +++ b/docs/linux/cli/README.md @@ -0,0 +1,17 @@ +# Linux 命令行 + +## :memo: 知识点 + +> 根据应用场景,将常见 Linux 命令分门别类的一一介绍。 +> +> 如果想快速学习,推荐参考这篇文章:[命令行的艺术(转载)](命令行的艺术.md) + +1. [查看 Linux 命令帮助信息](01.查看Linux命令帮助信息.md) - 关键词:`help`, `whatis`, `info`, `which`, `whereis`, `man` +2. [Linux 文件目录管理](02.Linux文件目录管理.md) - 关键词:`cd`, `ls`, `pwd`, `mkdir`, `rmdir`, `tree`, `touch`, `ln`, `rename`, `stat`, `file`, `chmod`, `chown`, `locate`, `find`, `cp`, `mv`, `rm` +3. [Linux 文件内容查看命令](03.Linux文件内容查看编辑.md) - 关键词:`cat`, `head`, `tail`, `more`, `less`, `sed`, `vi`, `grep` +4. [Linux 文件压缩和解压](04.Linux文件压缩和解压.md) - 关键词:`tar`, `gzip`, `zip`, `unzip` +5. [Linux 用户管理](05.Linux用户管理.md) - 关键词:`groupadd`, `groupdel`, `groupmod`, `useradd`, `userdel`, `usermod`, `passwd`, `su`, `sudo` +6. [Linux 系统管理](06.Linux系统管理.md) - 关键词:`reboot`, `exit`, `shutdown`, `date`, `mount`, `umount`, `ps`, `kill`, `systemctl`, `service`, `crontab` +7. [Linux 网络管理](07.Linux网络管理.md) - 关键词:关键词:`curl`, `wget`, `telnet`, `ip`, `hostname`, `ifconfig`, `route`, `ssh`, `ssh-keygen`, `firewalld`, `iptables`, `host`, `nslookup`, `nc`/`netcat`, `ping`, `traceroute`, `netstat` +8. [Linux 硬件管理](08.Linux硬件管理.md) - 关键词:`df`, `du`, `top`, `free`, `iotop` +9. [Linux 软件管理](09.Linux软件管理.md) - 关键词:`rpm`, `yum`, `apt-get` diff --git a/docs/linux/命令行的艺术.md b/docs/linux/cli/命令行的艺术.md similarity index 98% rename from docs/linux/命令行的艺术.md rename to docs/linux/cli/命令行的艺术.md index ea9ceb4..db85717 100644 --- a/docs/linux/命令行的艺术.md +++ b/docs/linux/cli/命令行的艺术.md @@ -1,6 +1,6 @@ > 转载自 https://github.com/jlevy/the-art-of-command-line -*[Čeština](README-cs.md) ∙ [Deutsch](README-de.md) ∙ [Ελληνικά](README-el.md) ∙ [English](README.md) ∙ [Español](README-es.md) ∙ [Français](README-fr.md) ∙ [Indonesia](README-id.md) ∙ [Italiano](README-it.md) ∙ [日本語](README-ja.md) ∙ [한국어](README-ko.md) ∙ [Português](README-pt.md) ∙ [Română](README-ro.md) ∙ [Русский](README-ru.md) ∙ [Slovenščina](README-sl.md) ∙ [Українська](README-uk.md) ∙ [简体中文](README-zh.md) ∙ [繁體中文](README-zh-Hant.md)* +*[Čeština](README-cs.md) ∙ [Deutsch](README-de.md) ∙ [Ελληνικά](README-el.md) ∙ [English](../README.md) ∙ [Español](README-es.md) ∙ [Français](README-fr.md) ∙ [Indonesia](README-id.md) ∙ [Italiano](README-it.md) ∙ [日本語](README-ja.md) ∙ [한국어](README-ko.md) ∙ [Português](README-pt.md) ∙ [Română](README-ro.md) ∙ [Русский](README-ru.md) ∙ [Slovenščina](README-sl.md) ∙ [Українська](README-uk.md) ∙ [简体中文](README-zh.md) ∙ [繁體中文](README-zh-Hant.md)* # 命令行的艺术 diff --git a/docs/deploy/linux基本配置.md b/docs/linux/ops/linux典型运维应用.md similarity index 94% rename from docs/deploy/linux基本配置.md rename to docs/linux/ops/linux典型运维应用.md index fd2ecb1..dbd873b 100644 --- a/docs/deploy/linux基本配置.md +++ b/docs/linux/ops/linux典型运维应用.md @@ -1,3 +1,4 @@ +# Linux 典型运维应用 ### 设置 Linux 启动模式 @@ -13,4 +14,4 @@ ```sh $ sed -i 's/id:5:initdefault:/id:3:initdefault:/' /etc/inittab -``` \ No newline at end of file +``` diff --git a/docs/linux/samba使用详解.md b/docs/linux/ops/samba使用详解.md similarity index 100% rename from docs/linux/samba使用详解.md rename to docs/linux/ops/samba使用详解.md diff --git a/docs/apollo/README.md b/docs/linux/ops/service/apollo/README.md similarity index 100% rename from docs/apollo/README.md rename to docs/linux/ops/service/apollo/README.md diff --git a/docs/apollo/apollo.xmind b/docs/linux/ops/service/apollo/apollo.xmind similarity index 100% rename from docs/apollo/apollo.xmind rename to docs/linux/ops/service/apollo/apollo.xmind diff --git a/docs/tool/elastic/README.md b/docs/linux/ops/service/elastic/README.md similarity index 100% rename from docs/tool/elastic/README.md rename to docs/linux/ops/service/elastic/README.md diff --git a/docs/tool/elastic/elastic-beats.md b/docs/linux/ops/service/elastic/elastic-beats.md similarity index 100% rename from docs/tool/elastic/elastic-beats.md rename to docs/linux/ops/service/elastic/elastic-beats.md diff --git a/docs/tool/elastic/elastic-kibana.md b/docs/linux/ops/service/elastic/elastic-kibana.md similarity index 100% rename from docs/tool/elastic/elastic-kibana.md rename to docs/linux/ops/service/elastic/elastic-kibana.md diff --git a/docs/tool/elastic/elastic-logstash.md b/docs/linux/ops/service/elastic/elastic-logstash.md similarity index 100% rename from docs/tool/elastic/elastic-logstash.md rename to docs/linux/ops/service/elastic/elastic-logstash.md diff --git a/docs/tool/elastic/elastic-quickstart.md b/docs/linux/ops/service/elastic/elastic-quickstart.md similarity index 100% rename from docs/tool/elastic/elastic-quickstart.md rename to docs/linux/ops/service/elastic/elastic-quickstart.md diff --git a/docs/linux/ops/service/gitlab/gitlab-ci.md b/docs/linux/ops/service/gitlab/gitlab-ci.md new file mode 100644 index 0000000..ccc7da2 --- /dev/null +++ b/docs/linux/ops/service/gitlab/gitlab-ci.md @@ -0,0 +1,3 @@ +# Gitlab + +![](https://docs.gitlab.com/ce/ci/img/cicd_pipeline_infograph.png) diff --git a/docs/tool/gitlab.md b/docs/linux/ops/service/gitlab/gitlab-install.md similarity index 91% rename from docs/tool/gitlab.md rename to docs/linux/ops/service/gitlab/gitlab-install.md index 1904354..7e42747 100644 --- a/docs/tool/gitlab.md +++ b/docs/linux/ops/service/gitlab/gitlab-install.md @@ -1,4 +1,4 @@ -# Gitlab +# Gitlab 安装 > 环境: > @@ -15,6 +15,7 @@ - [自签名证书](#自签名证书) - [创建证书](#创建证书) - [gitlab 配置](#gitlab-配置) +- [引申和引用](#引申和引用) @@ -75,17 +76,19 @@ docker pull docker.io/gitlab/gitlab-ce 启动 ``` -docker run --detach \ -     --hostname 127.0.0.1 \ -     --publish 8443:443 --publish 80:80 --publish 2222:22 \ -     --name gitlab \ -     --restart always \ -     --volume /home/gitlab/config:/etc/gitlab \ -     --volume /home/gitlab/logs:/var/log/gitlab \ -     --volume /home/gitlab/data:/var/opt/gitlab \ -     docker.io/gitlab/gitlab-ce:latest +docker run -d \ + --hostname gitlab.zp.io \ + --publish 8443:443 --publish 80:80 --publish 2222:22 \ + --name gitlab \ + --restart always \ + --volume $GITLAB_HOME/config:/etc/gitlab \ + --volume $GITLAB_HOME/logs:/var/log/gitlab \ + --volume $GITLAB_HOME/data:/var/opt/gitlab \ + gitlab/gitlab-ce ``` +![](http://dunwu.test.upcdn.net/snap/20190131150515.png) + ## 安装 gitlab-ci-multi-runner > 参考:https://docs.gitlab.com/runner/install/ @@ -289,3 +292,8 @@ sudo cp /etc/gitlab/ssl/gitlab.domain.com.crt /etc/gitlab/trusted-certs/ sudo gitlab-ctl reconfigure sudo gitlab-ctl restart ``` + +## 引申和引用 + +- **引申** + - [操作系统、运维部署总结系列](https://github.com/dunwu/OS) diff --git a/docs/linux/ops/service/gitlab/gitlab-quickstart.md b/docs/linux/ops/service/gitlab/gitlab-quickstart.md new file mode 100644 index 0000000..9ce9acb --- /dev/null +++ b/docs/linux/ops/service/gitlab/gitlab-quickstart.md @@ -0,0 +1,76 @@ +# Gitlab 快速教程 + +> 准备 +> +> Git - 如果不熟悉 Git ,可以先阅读:[Git 教程](https://github.com/dunwu/OS/tree/master/docs/git) + +## 创建你的 SSH key + +1. 使用 Gitlab 的第一步是生成你自己的 SSH 密钥对(Github 也类似)。 + +2. 登录 Gitlab + +3. 打开 **Profile settings**. + + ![Profile settings dropdown](https://docs.gitlab.com/ce/gitlab-basics/img/profile_settings.png) + +4. 跳转到 **SSH keys** tab 页 + + ![SSH Keys](https://docs.gitlab.com/ce/gitlab-basics/img/profile_settings_ssh_keys.png) + +5. 黏贴你的 SSH 公钥内容到 Key 文本框 + + ![Paste SSH public key](https://docs.gitlab.com/ce/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png) + +6. 为了便于识别,你可以为其命名 + + ![SSH key title](https://docs.gitlab.com/ce/gitlab-basics/img/profile_settings_ssh_keys_title.png) + +7. 点击 **Add key** 将 SSH 公钥添加到 GitLab + + ![SSH key single page](https://docs.gitlab.com/ce/gitlab-basics/img/profile_settings_ssh_keys_single_key.png) + +## 创建项目 + +![](http://dunwu.test.upcdn.net/snap/20190131150658.png) + +输入项目信息,点击 Create project 按钮,在 Gitlab 创建项目。 + +![](http://dunwu.test.upcdn.net/snap/20190131150759.png) + +## 克隆项目到本地 + +可以选择 SSH 或 HTTPS 方式克隆项目到本地(推荐 SSH) + +拷贝项目地址,然后在本地执行 `git clone ` + +![1548919326929](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1548919326929.png) + +## 创建 Issue + +依次点击 **Project’s Dashboard** > **Issues** > **New Issue** 可以新建 Issue + +![New issue from the issue list view](https://docs.gitlab.com/ce/user/project/issues/img/new_issue_from_tracker_list.png) + +在项目中直接添加 issue + +![New issue from the issues list](https://docs.gitlab.com/ce/user/project/issues/img/new_issue.png) + +在未关闭 issue 中,点击 **New Issue** 添加 issue + +![New issue from an open issue](https://docs.gitlab.com/ce/user/project/issues/img/new_issue_from_open_issue.png) + +通过项目面板添加 issue + +![New issue from a project's dashboard](https://docs.gitlab.com/ce/user/project/issues/img/new_issue_from_projects_dashboard.png) + +通过 issue 面板添加 issue + +![From the issue board](https://docs.gitlab.com/ce/user/project/issues/img/new_issue_from_issue_board.png) + +## 引申和引用 + +- **引申** + - [操作系统、运维部署总结系列](https://github.com/dunwu/OS) +- **引用** + - [官网](https://about.gitlab.com/) diff --git a/docs/tool/jdk.md b/docs/linux/ops/service/jdk.md similarity index 96% rename from docs/tool/jdk.md rename to docs/linux/ops/service/jdk.md index de3130c..73ff2ae 100644 --- a/docs/tool/jdk.md +++ b/docs/linux/ops/service/jdk.md @@ -130,7 +130,10 @@ $ rpm -ivh jdk-8u181-linux-x64.rpm (4)检验是否安装成功,执行 `java -version` 命令 -## 参考资料 +## 引申和引用 +- **引申** + - [操作系统、运维部署总结系列](https://github.com/dunwu/OS) +- **引用** - http://www.runoob.com/java/java-environment-setup.html - https://blog.csdn.net/deliciousion/article/details/78046007 diff --git a/docs/tool/jenkins.md b/docs/linux/ops/service/jenkins.md similarity index 92% rename from docs/tool/jenkins.md rename to docs/linux/ops/service/jenkins.md index 7fe8fd7..83067af 100644 --- a/docs/tool/jenkins.md +++ b/docs/linux/ops/service/jenkins.md @@ -119,8 +119,11 @@ sed -i 's/www.google.com/www.baidu.com/g' /root/.jenkins/updates/default.json sed -i '/^/s/.*/http:\/\/mirror.xmission.com\/jenkins\/updates\/update-center.json<\/url>/g' /root/.jenkins/hudson.model.UpdateCenter.xml ``` -## 参考资料 +## 引申和引用 -- https://jenkins.io/doc/pipeline/tour/getting-started/ -- https://www.cnblogs.com/austinspark-jessylu/p/6894944.html -- http://blog.csdn.net/jlminghui/article/details/54952148 +- **引申** + - [操作系统、运维部署总结系列](https://github.com/dunwu/OS) +- **引用** + - https://jenkins.io/doc/pipeline/tour/getting-started/ + - https://www.cnblogs.com/austinspark-jessylu/p/6894944.html + - http://blog.csdn.net/jlminghui/article/details/54952148 diff --git a/docs/tool/kafka.md b/docs/linux/ops/service/kafka.md similarity index 97% rename from docs/tool/kafka.md rename to docs/linux/ops/service/kafka.md index 275e210..a10da69 100644 --- a/docs/tool/kafka.md +++ b/docs/linux/ops/service/kafka.md @@ -140,3 +140,8 @@ Topic:my-replicated-topic PartitionCount:1 ReplicationFactor:3 Configs: - leader - 负责指定分区的所有读取和写入的节点。每个节点将成为随机选择的分区部分的领导者。 - replicas - 是复制此分区日志的节点列表,无论它们是否为领导者,或者即使它们当前处于活动状态。 - isr - 是“同步”复制品的集合。这是副本列表的子集,该列表当前处于活跃状态并且已经被领导者捕获。 + +## 引申和引用 + +- **引申** + - [操作系统、运维部署总结系列](https://github.com/dunwu/OS) diff --git a/docs/tool/nexus.md b/docs/linux/ops/service/nexus.md similarity index 95% rename from docs/tool/nexus.md rename to docs/linux/ops/service/nexus.md index 3167df7..a481534 100644 --- a/docs/tool/nexus.md +++ b/docs/linux/ops/service/nexus.md @@ -193,8 +193,11 @@ $ mvn clean package -Dmaven.skip.test=true -P zp $ mvn clean deploy -Dmaven.skip.test=true -P zp ``` -## 参考资料 +## 引申和引用 -- https://www.cnblogs.com/hoobey/p/6102382.html -- https://blog.csdn.net/wzygis/article/details/49276779 -- https://blog.csdn.net/clj198606061111/article/details/52200928 +- **引申** + - [操作系统、运维部署总结系列](https://github.com/dunwu/OS) +- **引用** + - https://www.cnblogs.com/hoobey/p/6102382.html + - https://blog.csdn.net/wzygis/article/details/49276779 + - https://blog.csdn.net/clj198606061111/article/details/52200928 diff --git a/docs/tool/nodejs.md b/docs/linux/ops/service/nodejs.md similarity index 90% rename from docs/tool/nodejs.md rename to docs/linux/ops/service/nodejs.md index 3de6cda..4df849d 100644 --- a/docs/tool/nodejs.md +++ b/docs/linux/ops/service/nodejs.md @@ -53,3 +53,8 @@ nvm use 8.9.4 ## 脚本 | [安装脚本](https://github.com/dunwu/OS/tree/master/codes/deploy/tool/nodejs) | + +## 引申和引用 + +- **引申** + - [操作系统、运维部署总结系列](https://github.com/dunwu/OS) diff --git a/docs/tool/rocketmq.md b/docs/linux/ops/service/rocketmq.md similarity index 92% rename from docs/tool/rocketmq.md rename to docs/linux/ops/service/rocketmq.md index d90e18f..3f44d52 100644 --- a/docs/tool/rocketmq.md +++ b/docs/linux/ops/service/rocketmq.md @@ -117,7 +117,10 @@ brokerIP1 = 10.10.30.63 nohup sh bin/mqbroker -n localhost:9876 -c conf/broker.conf & ``` -## 参考资料 +## 引申和引用 -- [RocketMQ 官方文档](http://rocketmq.apache.org/docs/quick-start/) -- [RocketMQ 搭建及刨坑](http://laciagin.me/2017/12/07/RocketMQ%E6%90%AD%E5%BB%BA%E5%8F%8A%E5%88%A8%E5%9D%91/) +- **引申** + - [操作系统、运维部署总结系列](https://github.com/dunwu/OS) +- **引用** + - [RocketMQ 官方文档](http://rocketmq.apache.org/docs/quick-start/) + - [RocketMQ 搭建及刨坑](http://laciagin.me/2017/12/07/RocketMQ%E6%90%AD%E5%BB%BA%E5%8F%8A%E5%88%A8%E5%9D%91/) diff --git a/docs/tool/svn.md b/docs/linux/ops/service/svn.md similarity index 100% rename from docs/tool/svn.md rename to docs/linux/ops/service/svn.md diff --git a/docs/tool/tomcat.md b/docs/linux/ops/service/tomcat.md similarity index 90% rename from docs/tool/tomcat.md rename to docs/linux/ops/service/tomcat.md index ce8019d..c6c7f88 100644 --- a/docs/tool/tomcat.md +++ b/docs/linux/ops/service/tomcat.md @@ -45,3 +45,8 @@ cd /opt/tomcat/apache-tomcat-8.5.28/bin ## 脚本 | [安装脚本](https://github.com/dunwu/OS/tree/master/codes/deploy/tool/tomcat) | + +## 引申和引用 + +- **引申** + - [操作系统、运维部署总结系列](https://github.com/dunwu/OS) diff --git a/docs/tool/zookeeper.md b/docs/linux/ops/service/zookeeper.md similarity index 94% rename from docs/tool/zookeeper.md rename to docs/linux/ops/service/zookeeper.md index cd4c009..3323293 100644 --- a/docs/tool/zookeeper.md +++ b/docs/linux/ops/service/zookeeper.md @@ -89,3 +89,8 @@ $ bin/zkServer.sh stop ``` > 本节安装内容参考:[Zookeeper 安装](https://www.w3cschool.cn/zookeeper/zookeeper_installation.html) + +## 引申和引用 + +- **引申** + - [操作系统、运维部署总结系列](https://github.com/dunwu/OS) diff --git a/docs/python.md b/docs/linux/scripts/python.md similarity index 100% rename from docs/python.md rename to docs/linux/scripts/python.md diff --git a/docs/shell.md b/docs/linux/scripts/shell.md similarity index 100% rename from docs/shell.md rename to docs/linux/scripts/shell.md diff --git a/docs/git/README.md b/docs/linux/tool/git/README.md similarity index 100% rename from docs/git/README.md rename to docs/linux/tool/git/README.md diff --git a/docs/git/advanced/git-flow.md b/docs/linux/tool/git/advanced/git-flow.md similarity index 100% rename from docs/git/advanced/git-flow.md rename to docs/linux/tool/git/advanced/git-flow.md diff --git a/docs/git/appendix/git-command.md b/docs/linux/tool/git/appendix/git-command.md similarity index 100% rename from docs/git/appendix/git-command.md rename to docs/linux/tool/git/appendix/git-command.md diff --git a/docs/git/appendix/git-faq.md b/docs/linux/tool/git/appendix/git-faq.md similarity index 100% rename from docs/git/appendix/git-faq.md rename to docs/linux/tool/git/appendix/git-faq.md diff --git a/docs/git/appendix/git-resource.md b/docs/linux/tool/git/appendix/git-resource.md similarity index 97% rename from docs/git/appendix/git-resource.md rename to docs/linux/tool/git/appendix/git-resource.md index e5cf4e0..08fccea 100644 --- a/docs/git/appendix/git-resource.md +++ b/docs/linux/tool/git/appendix/git-resource.md @@ -19,7 +19,7 @@ * [Git中文教程](https://github.com/geeeeeeeeek/git-recipes) * [廖雪峰的Git教程](https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000) -* [有关 git 的学习资料](https://github.com/xirong/my-git) +* [有关 git 的学习资源](https://github.com/xirong/my-git) ## 文章 @@ -46,4 +46,4 @@ * [GitUp](https://github.com/git-up/GitUp) - 一个新的Git客户端,在处理Git的复杂性上有自己的特点 * [gitx-dev](https://rowanj.github.io/gitx/) - 图形化的Git客户端 OS X * [Source Tree](https://www.sourcetreeapp.com/) - 免费的图形化Git客户端 Windows & OS X -* [Tower](http://www.git-tower.com/) - 图形化Git客户端 OS X(付费) \ No newline at end of file +* [Tower](http://www.git-tower.com/) - 图形化Git客户端 OS X(付费) diff --git a/docs/git/appendix/github-git-cheat-sheet.pdf b/docs/linux/tool/git/appendix/github-git-cheat-sheet.pdf similarity index 100% rename from docs/git/appendix/github-git-cheat-sheet.pdf rename to docs/linux/tool/git/appendix/github-git-cheat-sheet.pdf diff --git a/docs/git/basics/git-configuration.md b/docs/linux/tool/git/basics/git-configuration.md similarity index 100% rename from docs/git/basics/git-configuration.md rename to docs/linux/tool/git/basics/git-configuration.md diff --git a/docs/git/basics/git-installation.md b/docs/linux/tool/git/basics/git-installation.md similarity index 100% rename from docs/git/basics/git-installation.md rename to docs/linux/tool/git/basics/git-installation.md diff --git a/docs/git/git-quickstart.md b/docs/linux/tool/git/git-quickstart.md similarity index 100% rename from docs/git/git-quickstart.md rename to docs/linux/tool/git/git-quickstart.md diff --git a/docs/git/git.xmind b/docs/linux/tool/git/git.xmind similarity index 100% rename from docs/git/git.xmind rename to docs/linux/tool/git/git.xmind diff --git a/docs/vim.md b/docs/linux/tool/vim.md similarity index 100% rename from docs/vim.md rename to docs/linux/tool/vim.md