在阅读本文以前,您必须对 Docker 的中涉及的基本概念以及常见命令有必定了解,本文侧重实战,不会对相关概念详述。node
同时请确保您本地开发机器已完成以下安装:nginx
注:本文实验环境是 Ubuntu 18.04 LTS。若是您的机器是 Window,也能够把 Docker 装在虚拟机或服务器上。git
开始以前要先准备一个须要 Docker 容器化的 ASP.NET Core 应用程序,用于下面的操做演示。这里我用 .NET Core CLI 快速搭建一个全新的 Web API 项目。github
启动 VS Code,打开集成终端,输入以下命令:web
dotnet new webapi -o TodoApi code -r TodoApi
以上便建立了一个名为TodoApi
的 Web API 样板项目。redis
打开集成终端,输入dotnet run
命令编译运行程序,而后打开浏览器跳转到 URL http://localhost:5000/api/values
,如正常返回以下 JSON 数据,说明应用程序本地成功运行。sql
["value1","value2"]
如今让咱们更进一步,在 Docker 中构建并运行该应用程序。docker
Dockerfile 是一个文本文件,其用来定义单个容器的内容和启动行为,按顺序包含构建镜像所需的全部指令。Docker 会经过读取 Dockerfile 中的指令自动构建镜像。数据库
在项目TodoApi
根目录中,建立一个名为Dockerfile
的文件,并粘贴如下内容:json
FROM microsoft/dotnet:2.2-sdk AS build-env WORKDIR /app # Copy csproj and restore as distinct layers COPY *.csproj ./ RUN dotnet restore # Copy everything else and build COPY . ./ RUN dotnet publish -c Release -o out # Build runtime image FROM microsoft/dotnet:2.2-aspnetcore-runtime WORKDIR /app COPY --from=build-env /app/out . ENTRYPOINT ["dotnet", "TodoApi.dll"]
FROM
指令必须放在第一位,用于初始化镜像,为后面的指令设置基础镜像。WORKDIR
指令为其余指令设置工做目录,若是不存在,则会建立该目录。COPY
指令会从源路径复制新文件或目录,并将它们添加到路径目标容器的文件系统中。RUN
指令能够在当前镜像之上的新 层 中执行任何命令并提交结果,生成的已提交镜像将用于 Dockerfile 中的下一步。ENTRYPOINT
指令支持以可执行文件的形式运行容器。有关 Dockerfile 中指令用法的更多信息请参阅 Dockerfile reference。
同时,为了不构建项目中的一些调试生成文件,能够在项目文件夹中新增.dockerignore
文件,并粘贴以下内容:
bin\ obj\
在项目TodoApi
根目录中,打开集成终端,执行以下命令构建容器镜像:
docker build -t todoapi .
-t
参数用来指定镜像的名字及标签,一般是name:tag
或者name
格式。本例todoapi
即是咱们给镜像起的名字,没有设置标签即便用默认标签latest
。
如命令执行成功,终端会有相似以下输出:
$ docker build -t todoapi . Sending build context to Docker daemon 1.137MB Step 1/10 : FROM microsoft/dotnet:2.2-sdk AS build-env 2.2-sdk: Pulling from microsoft/dotnet e79bb959ec00: Pull complete d4b7902036fe: Pull complete 1b2a72d4e030: Pull complete d54db43011fd: Pull complete b3ae1535ac68: Pull complete f04cf82b07ad: Pull complete 6f91a9d92092: Pull complete Digest: sha256:c443ff79311dde76cb1acf625ae47581da45aad4fd66f84ab6ebf418016cc008 Status: Downloaded newer image for microsoft/dotnet:2.2-sdk ---> e268893be733 Step 2/10 : WORKDIR /app ---> Running in c7f62130f331 Removing intermediate container c7f62130f331 ---> e8b6a73d3d84 Step 3/10 : COPY *.csproj ./ ---> cfa03afa6003 Step 4/10 : RUN dotnet restore ---> Running in d96a9b89e4a9 Restore completed in 924.67 ms for /app/TodoApi.csproj. Removing intermediate container d96a9b89e4a9 ---> 14d5d32d40b6 Step 5/10 : COPY . ./ ---> b1242ea0b0b8 Step 6/10 : RUN dotnet publish -c Release -o out ---> Running in 37c8eb07c86e Microsoft (R) Build Engine version 16.0.450+ga8dc7f1d34 for .NET Core Copyright (C) Microsoft Corporation. All rights reserved. Restore completed in 663.74 ms for /app/TodoApi.csproj. TodoApi -> /app/bin/Release/netcoreapp2.2/TodoApi.dll TodoApi -> /app/out/ Removing intermediate container 37c8eb07c86e ---> 6238f4c1cf07 Step 7/10 : FROM microsoft/dotnet:2.2-aspnetcore-runtime 2.2-aspnetcore-runtime: Pulling from microsoft/dotnet 27833a3ba0a5: Pull complete 25dbf7dc93e5: Pull complete 0ed9cb15d3b8: Pull complete 874ea13b7488: Pull complete Digest: sha256:ffd756d34bb0f976ba5586f6c88597765405af8014ae51b34811992b46ba40e8 Status: Downloaded newer image for microsoft/dotnet:2.2-aspnetcore-runtime ---> cb2dd04458bc Step 8/10 : WORKDIR /app ---> Running in b0a3826d346b Removing intermediate container b0a3826d346b ---> 4218db4cc2f5 Step 9/10 : COPY --from=build-env /app/out . ---> 765168aa2c7a Step 10/10 : ENTRYPOINT ["dotnet", "TodoApi.dll"] ---> Running in f93bcaf5591f Removing intermediate container f93bcaf5591f ---> 046226f5e9cb Successfully built 046226f5e9cb Successfully tagged todoapi:latest
若是您的机器是第一次构建,速度可能会有些慢,由于要从 Docker Hub 上拉取应用依赖的
dotnet-sdk
和aspnetcore-runtime
基础镜像。
构建完成后,咱们能够经过docker images
命令确认本地镜像仓库是否存在咱们构建的镜像todoapi
。
REPOSITORY TAG IMAGE ID CREATED SIZE todoapi latest c92a82f0efaa 19 hours ago 260MB microsoft/dotnet 2.2-sdk 5e09f77009fa 26 hours ago 1.74GB microsoft/dotnet 2.2-aspnetcore-runtime 08ed21b5758c 26 hours ago 260MB ...
容器镜像构建完成后,就可使用docker run
命令运行容器了,有关该命令参数的更多信息请参阅 Reference - docker run 。
开发环境下,一般会经过docker run --rm -it
命令运行应用容器,具体命令以下:
docker run --rm -it -p 5000:80 todoapi
-it
参数表示以交互模式运行容器并为容器从新分配一个伪输入终端,方便查看输出调试程序。--rm
参数表示将会在容器退出后自动删除当前容器,开发模式下经常使用参数。-p
参数表示会将本地计算机上的5000
端口映射到容器中的默认80
端口,端口映射的关系为host:container
。todoapi
即是咱们要启动的本地镜像名称。如命令执行成功,终端会有相似以下输出:
$ docker run -it --rm -p 5000:80 todoapi warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35] No XML encryptor configured. Key {1a78d899-738b-4aea-a7d6-777302933f38} may be persisted to storage in unencrypted form. Hosting environment: Production Content root path: /app Now listening on: http://[::]:80 Application started. Press Ctrl+C to shut down.
生产环境下,一般会经过docker run -d
命令运行应用容器,具体命令以下:
docker run -d --restart=always --name myapp -p 5000:80 todoapi
-d
参数表示会将容器做为服务启动,不须要终端交互。--name
参数用来指定容器名称,本例指定容器名称为myapp
。--restart
是一个面向生产环境的参数,用来指定容器非正常退出时的重启策略,本例always
表示始终从新启动容器,其余可选策略请参考 Restart policies (--restart)。如命令执行成功,终端会有相似以下输出:
$ docker run -d --restart=always --name myapp -p 5000:80 todoapi e3d747d9d2b4cd14b2acb24f81bea9312f89c4eb689dba5f6559950c91db1600
容器启动后,在 Web 浏览器中再次访问http://localhost:5000/api/values
,应该会和本地测试同样返回以下 JSON 数据:
["value1","value2"]
至此,咱们的 ASP.NET Core 应用就成功运行在 Docker 容器中了。
目前咱们建立的演示项目TodoApi
过于简单,真实的生产项目确定会涉及更多其余的依赖。例如:关系数据库 Mysql、文档数据库 MongoDB、分布式缓存 Redis、消息队列 RabbitMQ 等各类服务。
还有就是,生产环境咱们通常不会将 ASP.NET Core 应用程序的宿主服务器 Kestrel 直接暴露给用户,一般是在前面加一个反向代理服务 Nginx。
这些依赖服务还要像传统部署方式那样,一个一个单独配置部署吗?不用的,由于它们自己也是能够被容器化的,因此咱们只要考虑如何把各个相互依赖的容器联系到一块儿,这就涉及到容器编排,而 Docker Compose 正是用来解决这一问题的,最终能够实现多容器应用的一键部署。
Docker Compose 是一个用于定义和运行多容器的 Docker 工具。其使用
YAML
文件来配置应用程序的服务,最终您只要使用一个命令就能够从配置中建立并启动全部服务。
Linux 系统下的安装过程大体分为如下几步:
Step1:运行以下命令下载 Compose 最新稳定版本,截止发稿前最新版本为1.24.0
。
sudo curl -L "https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
Step2:对下载完成的二进制程序添加可执行权限。
sudo chmod +x /usr/local/bin/docker-compose
Step3:测试安装是否成功。
$ docker-compose --version docker-compose version 1.24.0, build 0aa59064
若您在安装过程当中遇到问题,或是其余系统安装请参阅 Install Docker Compose。
如今来改造一下咱们的演示项目TodoApi
,添加 Redis 分布式缓存、使用 Nginx 作反向代理,准备构建一个具以下图所示架构的多容器应用。
在TodoApi
项目根目录下,打开集成终端,输入以下命令新增 Redis 依赖包。
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis --version 2.2.0
修改应用启动配置文件Startup.cs
中的ConfigureServices
方法:
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddStackExchangeRedisCache(options => { options.Configuration = Configuration.GetConnectionString("Redis"); }); services.AddHttpContextAccessor(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }
在TodoApi
项目Controllers
目录下新建控制器HelloController
,具体代码以下所示:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Distributed; namespace TodoApi.Controllers { [Route("api/[controller]")] [ApiController] public class HelloController : ControllerBase { private readonly IDistributedCache _distributedCache; private readonly IHttpContextAccessor _httpContextAccessor; public HelloController( IDistributedCache distributedCache, IHttpContextAccessor httpContextAccessor) { _distributedCache = distributedCache; _httpContextAccessor = httpContextAccessor; } [HttpGet] public ActionResult<string> Get() { var connection = _httpContextAccessor.HttpContext.Connection; var ipv4 = connection.LocalIpAddress.MapToIPv4().ToString(); var message = $"Hello from Docker Container:{ipv4}"; return message; } [HttpGet("{name}")] public ActionResult<string> Get(string name) { var defaultKey = $"hello:{name}"; _distributedCache.SetString(defaultKey, $"Hello {name} form Redis"); var message = _distributedCache.GetString(defaultKey); return message; } } }
以上控制器,提供了两个接口/api/hello
和/api/hello/{name}
,分别用来测试 Nginx 负载均衡和 Redis 的联通性。
准备工做就绪,下面咱们就可使用 Docker Compose 来编排容器。
一样是在TodoApi
项目根目录中,建立一个名为docker-compose.yml
的文件,并粘贴如下内容:
version: "3.7" services: myproject-todoapi-1: container_name: my-todoapi-1 build: context: . dockerfile: Dockerfile restart: always ports: - "5001:80" volumes: - ./appsettings.json:/app/appsettings.json myproject-todoapi-2: container_name: my-todoapi-2 build: context: . dockerfile: Dockerfile restart: always ports: - "5002:80" volumes: - ./appsettings.json:/app/appsettings.json myproject-todoapi-3: container_name: my-todoapi-3 build: context: . dockerfile: Dockerfile restart: always ports: - "5003:80" volumes: - ./appsettings.json:/app/appsettings.json myproject-nginx: container_name: my-nginx image: nginx restart: always ports: - "80:80" volumes: - ./conf/nginx.conf:/etc/nginx/conf.d/default.conf myproject-redis: container_name: my-redis image: redis restart: always ports: - "6379:80" volumes: - ./conf/redis.conf:/etc/redis/redis.conf
其中version
用来指定 Compose 文件版本号,3.7
是目前最新版本,具体哪些版本对应哪些特定的 Docker 引擎版本请参阅 Compose file versions and upgrading。
Compose 中强化了服务的概念,简单地理解就是, 服务是一种用于生产环境的容器。一个多容器 Docker 应用由若干个服务组成,如上文件即定义了 5 个服务:
myproject-todoapi-1
、myproject-todoapi-2
和myproject-todoapi-3
myproject-reverse-proxy
myproject-redis
以上 5 个服务的配置参数相差无几、也很简单,我就不展开叙述,不清楚的能够参阅 Compose file reference。
这里只讲一个配置参数volumes
:
咱们知道,容器中的文件在宿主机上存在形式复杂,修改文件须要先经过以下命令进入容器后操做。
docker exec -it <CONTAINER ID/NAMES> /bin/bash
容器一旦删除,其内部配置以及产生的数据也会丢失。
为了解决这些问题,Docker 引入了数据卷 volumes 机制。即 Compose 中 volumes 参数用来将宿主机的某个目录或文件映射挂载到 Docker 容器内部的对应的目录或文件,一般被用来灵活挂载配置文件或持久化容器产生的数据。
PS:本身动手编写
docker-compose.yml
的时候,能够尝试实验更多场景。好比:新增一个 MySQL 依赖服务、把容器内产生的数据持久化到宿主机等等。
接下来,须要根据如上docker-compose.yml
文件中涉及的volumes
配置建立三个配置文件。要知道,它们最终是须要被注入到 Docker 容器中的。
首先,在TodoApi
项目根目录中,建立三个应用服务myproject-todoapi-*
须要的程序配置文件appsettings.json
,具体内容以下:
"ConnectionStrings": { "Redis": "myproject-redis:6379,password=todoapi@2019" },
以上配置,指定了 Redis 服务myproject-redis
的链接字符串,其中myproject-redis
能够看到是 Redis 服务的服务名称,当该配置文件注入到 Docker 容器中后,会自动解析为容器内部 IP,同时考虑到 Redis 服务的安全性,为其指定了密码,即password=todoapi@2019
。
而后,在TodoApi
项目根目录中建立一个子目录conf
,用来存放 Nginx 和 Redis 的配置文件。
mkdir conf && cd conf
先来建立 Redis 服务myproject-redis
的配置文件。
能够经过以下命令,下载一个 Redis 官方提供的标准配置文件redis.conf
:
wget http://download.redis.io/redis-stable/redis.conf
而后打开下载后的redis.conf
文件,找到SECURITY
节点,根据如上应用服务的 Redis 链接字符串信息,启用并改下密码:
requirepass todoapi@2019
再来建立 Nginx 服务myproject-nginx
的配置文件。
在conf
目录中,建立一个名为nginx.conf
的配置文件,并粘贴以下内容:
upstream todoapi { server myproject-todoapi-1:80; server myproject-todoapi-2:80; server myproject-todoapi-3:80; } server { listen 80; location / { proxy_pass http://todoapi; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
以上配置,是一个 Nginx 中具有负载均衡的代理配置,其默认采用轮循策略将请求转发给 Docker 服务myproject-todoapi-1
、myproject-todoapi-2
和myproject-todoapi-3
。
通过以上几个小节,容器编排的过程就完成了,接下来就能够直接定义并启动咱们建立的多容器应用实例了。
切换到docker-compose.yml
文件所在的目录,也就是TodoApi
项目的根目录,执行以下命令:
docker-compose up -d
如命令执行成功,终端最后会有相似以下输出:
...... Creating my-todoapi-1 ... done Creating my-redis ... done Creating my-todoapi-3 ... done Creating my-nginx ... done Creating my-todoapi-2 ... done
至此,咱们的多容器应用就已经在运行了,能够经过docker-compose ps
命令来确认下。
$ docker-compose ps Name Command State Ports -------------------------------------------------------------------------------------- my-nginx nginx -g daemon off; Up 0.0.0.0:80->80/tcp my-redis docker-entrypoint.sh redis ... Up 6379/tcp, 0.0.0.0:6379->80/tcp my-todoapi-1 dotnet TodoApi.dll Up 0.0.0.0:5001->80/tcp my-todoapi-2 dotnet TodoApi.dll Up 0.0.0.0:5002->80/tcp my-todoapi-3 dotnet TodoApi.dll Up 0.0.0.0:5003->80/tcp
能够经过连续三次请求/api/hello
接口测试应用的负载均衡。
curl http://localhost/api/hello curl http://localhost/api/hello curl http://localhost/api/hello // Output: Hello from Docker Container:172.30.0.2 Hello from Docker Container:172.30.0.4 Hello from Docker Container:172.30.0.5
三个应用服务分别部署在不一样容器中,因此理论上来说,他们的容器内部 IP 也是不一样的,因此/api/hello
接口每次输出信息不会相同。
请求/api/hello/{name}
接口测试 Redis 服务连通性。
curl http://localhost/api/hello/esofar // Output: Hello esofar form Redis
本文从零构建了一个 ASP.NET Core 应用,并经过 Docker 部署,而后由浅入深,引入 Docker Compose 演示了多容器应用的部署过程。经过本文的实战您能够更深刻地了解 Docker。本文涉及的代码已托管到如下地址,您在实验过程当中遇到问题能够参考。
https://github.com/esofar/dockerize-aspnetcore-samples
$ docker --help Usage: docker [OPTIONS] COMMAND A self-sufficient runtime for containers Options: --config string Location of client config files (default "/root/.docker") -D, --debug Enable debug mode -H, --host list Daemon socket(s) to connect to -l, --log-level string Set the logging level ("debug"|"info"|"warn"|"error"|"fatal") (default "info") --tls Use TLS; implied by --tlsverify --tlscacert string Trust certs signed only by this CA (default "/root/.docker/ca.pem") --tlscert string Path to TLS certificate file (default "/root/.docker/cert.pem") --tlskey string Path to TLS key file (default "/root/.docker/key.pem") --tlsverify Use TLS and verify the remote -v, --version Print version information and quit Management Commands: builder Manage builds config Manage Docker configs container Manage containers engine Manage the docker engine image Manage images network Manage networks node Manage Swarm nodes plugin Manage plugins secret Manage Docker secrets service Manage services stack Manage Docker stacks swarm Manage Swarm system Manage Docker trust Manage trust on Docker images volume Manage volumes Commands: attach Attach local standard input, output, and error streams to a running container build Build an image from a Dockerfile commit Create a new image from a container's changes cp Copy files/folders between a container and the local filesystem create Create a new container diff Inspect changes to files or directories on a container's filesystem events Get real time events from the server exec Run a command in a running container export Export a container's filesystem as a tar archive history Show the history of an image images List images import Import the contents from a tarball to create a filesystem image info Display system-wide information inspect Return low-level information on Docker objects kill Kill one or more running containers load Load an image from a tar archive or STDIN login Log in to a Docker registry logout Log out from a Docker registry logs Fetch the logs of a container pause Pause all processes within one or more containers port List port mappings or a specific mapping for the container ps List containers pull Pull an image or a repository from a registry push Push an image or a repository to a registry rename Rename a container restart Restart one or more containers rm Remove one or more containers rmi Remove one or more images run Run a command in a new container save Save one or more images to a tar archive (streamed to STDOUT by default) search Search the Docker Hub for images start Start one or more stopped containers stats Display a live stream of container(s) resource usage statistics stop Stop one or more running containers tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE top Display the running processes of a container unpause Unpause all processes within one or more containers update Update configuration of one or more containers version Show the Docker version information wait Block until one or more containers stop, then print their exit codes
$ docker-compose --help Define and run multi-container applications with Docker. Usage: docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...] docker-compose -h|--help Options: -f, --file FILE Specify an alternate compose file (default: docker-compose.yml) -p, --project-name NAME Specify an alternate project name (default: directory name) --verbose Show more output --log-level LEVEL Set log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) --no-ansi Do not print ANSI control characters -v, --version Print version and exit -H, --host HOST Daemon socket to connect to --tls Use TLS; implied by --tlsverify --tlscacert CA_PATH Trust certs signed only by this CA --tlscert CLIENT_CERT_PATH Path to TLS certificate file --tlskey TLS_KEY_PATH Path to TLS key file --tlsverify Use TLS and verify the remote --skip-hostname-check Don't check the daemon's hostname against the name specified in the client certificate --project-directory PATH Specify an alternate working directory (default: the path of the Compose file) --compatibility If set, Compose will attempt to convert keys in v3 files to their non-Swarm equivalent Commands: build Build or rebuild services bundle Generate a Docker bundle from the Compose file config Validate and view the Compose file create Create services down Stop and remove containers, networks, images, and volumes events Receive real time events from containers exec Execute a command in a running container help Get help on a command images List images kill Kill containers logs View output from containers pause Pause services port Print the public port for a port binding ps List containers pull Pull service images push Push service images restart Restart services rm Remove stopped containers run Run a one-off command scale Set number of containers for a service start Start services stop Stop services top Display the running processes unpause Unpause services up Create and start containers version Show the Docker-Compose version information