在这里,我们将给大家分享关于Consul集群版容器化部署与应用集成的知识,让您更了解容器集群服务的本质,同时也会涉及到如何更有效地.NETCore+MySql+Nginx容器化部署、.netcore应
在这里,我们将给大家分享关于Consul 集群版容器化部署与应用集成的知识,让您更了解容器集群服务的本质,同时也会涉及到如何更有效地.NET Core+MySql+Nginx 容器化部署、.netcore应用容器化部署、angular应用容器化部署、ASP.NET Core 应用程序容器化、持续集成与 Kubernetes 集群部署(一)(转载)的内容。
本文目录一览:- Consul 集群版容器化部署与应用集成(容器集群服务)
- .NET Core+MySql+Nginx 容器化部署
- .netcore应用容器化部署
- angular应用容器化部署
- ASP.NET Core 应用程序容器化、持续集成与 Kubernetes 集群部署(一)(转载)
Consul 集群版容器化部署与应用集成(容器集群服务)
背景
由于公司目前的主要产品使用的注册中心是 consul
,consul
需要用集群来保证高可用,传统的方式(Nginx/HAProxy)会有单点故障问题,为了解决该问题,我开始研究如何只依赖 consul
做集群的注册的方式,经过一天的折腾,总算验证了可以通过集群版 ConsulClient 来进行集群注册,在部署实施过程中也遇到了一些问题,特此记录分享,希望能对有需要的同学有帮助。
主机版集群和 docker 版集群对比
client+server 转发模式的集群部署涉及到两种选择,第一种是直接主机模式部署,2 个 client
+3 个 server
,每个 consul
实例部署一台主机(适合土豪使用),此种模式的好处就是简单暴力,而且运维相对简单。此种模式的架构部署图如下:
我们选择的是另外一种经济节约模式,docker
化部署,好处就是节约资源,劣势就是要管理许多 docker
镜像,在有引入 k8s 这些容器管理平台之前,后续 docker
的运维会比较麻烦,这种模式的架构部署图如下:
通过以上两种模式的架构图我们很清楚的就能知道主机部署模式是最简单直接的,而 docker
的模式虽然节省了资源,但是加大了复杂性,增加了运维的难度。但是这种模式应该是在目前容器化的环境下很好的选择,原因很简单,因为充分的利用了资源,容器的运维可以交给容器运维平台去完成,比如 k8s 等。下面我们来实践下如何进行容器化的 consul
集群部署。
环境准备
这里准备了两台虚拟主机,由于是虚拟的主机,对外 ip 是一样的,所以我们以端口区分。
主机A:192.168.23.222:10385 内网ip:192.168.236.3
主机B:192.168.23.222:10585 内网ip:192.168.236.5
部署配置
步骤一:主机安装 Docker 环境 (以 Centos 为例)
yum install docker
步骤二:拉取 Consul 镜像进行部署
docker pull consul
步骤三:给主机 Docker 分配 ip 段,防止多主机 ip 重复
- 在主机 A 编辑
docker
的/etc/docker/daemon.json
文件,添加下面的内容
"bip": "172.17.1.252/24"
- 在主机 B 编辑
docker
的/etc/docker/daemon.json
文件,添加下面的内容
"bip": "172.17.2.252/24"
这里的配置是给主机的 docker
实例分配 ip,因为后续 docker
会进行跨主机注册,如果默认注册的话,docker
是用的主机内网,从而导致 ip 重复,所以这里手动进行 ip 分配,当然上述的 ip 配置你可以自定义。
步骤四:在主机 A 部署 Consul
Node1:
docker run -d --name=node_31 --restart=always \
-e ''CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}'' \
-p 11300:8300 \
-p 11301:8301 \
-p 11301:8301/udp \
-p 11302:8302/udp \
-p 11302:8302 \
-p 11400:8400 \
-p 11500:8500 \
-p 11600:8600 \
consul agent -server -join=172.17.1.1 -bootstrap-expect=3 -node=node31 \
-data-dir=/consul/data/ -client 0.0.0.0 -ui
这里重点说明几个参数:
--name
: 是 docker
容器的名字,每个容器实例不一样
-node
: 是 consul
节点的名字,每个节点不一样
-bootstrap-expect
: 是启动集群期望至少多少个节点,这里设置是 3 个。
-data-dir
: 是 consul
的数据中心的目录,必须给与 consul
读写权限,否则启动会报错。
启动成功后,执行命令查看 consul
的节点。
docker exec -t node_31 consul members
显示结果如下:
Node Address Status Type Build Protocol DC Segment
node31 172.17.1.1:8301 alive server 1.6.2 2 dc1 <all>
这说明第一个节点正常启动了,接下来正常启动主机 A 剩下的节点。
Node2:
docker run -d --name=node_32 --restart=always \
-e ''CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}'' \
-p 9300:8300 \
-p 9301:8301 \
-p 9301:8301/udp \
-p 9302:8302/udp \
-p 9302:8302 \
-p 9400:8400 \
-p 9500:8500 \
-p 9600:8600 \
consul agent -server -join=172.17.1.1 -bootstrap-expect=3 -node=node32 \
-data-dir=/consul/data/ -client 0.0.0.0 -ui
Node3:
docker run -d --name=node_33 --restart=always \
-e ''CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}'' \
-p 10300:8300 \
-p 10301:8301 \
-p 10301:8301/udp \
-p 10302:8302/udp \
-p 10302:8302 \
-p 10400:8400 \
-p 10500:8500 \
-p 10600:8600 \
consul agent -server -join=172.17.1.1 -bootstrap-expect=3 -node=node33 \
-data-dir=/consul/data/ -client 0.0.0.0 -ui
三个节点启动完毕后,执行命令,查看节点的状态:
docker exec -t node_31 consul operator raft list-peers
结果如下:
Node ID Address State Voter RaftProtocol
node32 ee186aef-5f8a-976b-2a33-b20bf79e7da9 172.17.1.2:8300 follower true 3
node33 d86b6b92-19e6-bb00-9437-f988b6dac4b2 172.17.1.3:8300 follower true 3
node31 0ab60093-bed5-be77-f551-6051da7fe790 172.17.1.1:8300 leader true 3
这里已经显示,三个 server
的节点已经完成了集群部署,并且选举了 node_31
作为主节点。最后给该主机集群部署一个 client 就大功告成了。
Node4(client 节点)
docker run -d --name=node_34 --restart=always \
-e ''CONSUL_LOCAL_CONFIG={"leave_on_terminate": true}'' \
-p 8300:8300 \
-p 8301:8301 \
-p 8301:8301/udp \
-p 8302:8302/udp \
-p 8302:8302 \
-p 8400:8400 \
-p 8500:8500 \
-p 8600:8600 \
consul agent -retry-join=172.17.1.1 \
-node-id=$(uuidgen | awk ''{print tolower($0)}'') \
-node=node34 -client 0.0.0.0 -ui
执行 docker exec -t node_31 consul members
命令,结果如下:
Node Address Status Type Build Protocol DC Segment
node31 172.17.1.1:8301 alive server 1.6.2 2 dc1 <all>
node32 172.17.1.2:8301 alive server 1.6.2 2 dc1 <all>
node33 172.17.1.3:8301 alive server 1.6.2 2 dc1 <all>
node34 172.17.1.4:8301 alive client 1.6.2 2 dc1 <default>
这里说明,主机 A 的 consul
节点全部启动完成,并且完成了集群部署,可以说这就是一个单主机版的 consul
集群,那么接下来我们要做的就是把主机 B 的 consul
加入到主机 A 的集群中即可。
步骤五:在主机 B 部署 Consul
Node5
docker run -d --name=node_51 --restart=always \
-e ''CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}'' \
-p 11300:8300 \
-p 11301:8301 \
-p 11301:8301/udp \
-p 11302:8302/udp \
-p 11302:8302 \
-p 11400:8400 \
-p 11500:8500 \
-p 11600:8600 \
consul agent -server -join=172.17.1.1 -bootstrap-expect=3 -node=node_51 \
-data-dir=/consul/data/ -client 0.0.0.0 -ui
Node6
docker run -d --name=node_52 --restart=always \
-e ''CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}'' \
-p 9300:8300 \
-p 9301:8301 \
-p 9301:8301/udp \
-p 9302:8302/udp \
-p 9302:8302 \
-p 9400:8400 \
-p 9500:8500 \
-p 9600:8600 \
consul agent -server -join=172.17.1.1 -bootstrap-expect=3 -node=node_52 \
-data-dir=/consul/data/ -client 0.0.0.0 -ui
Node7
docker run -d --name=node_53 --restart=always \
-e ''CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}'' \
-p 10300:8300 \
-p 10301:8301 \
-p 10301:8301/udp \
-p 10302:8302/udp \
-p 10302:8302 \
-p 10400:8400 \
-p 10500:8500 \
-p 10600:8600 \
consul agent -server -join=172.17.1.1 -bootstrap-expect=3 -node=node_53 \
-data-dir=/consul/data/ -client 0.0.0.0 -ui
主机 B 的三个 server 节点部署完成后,我们执行命令 docker exec -t node_51 consul members
查看下集群节点状态
Node Address Status Type Build Protocol DC Segment
node_51 172.17.2.1:8301 alive server 1.6.2 2 dc1 <all>
为什么只有 node_51
这个单独的节点呢?是不是节点的问题?我们在主机 B 中查询同样查询一下,结果如下:
node31 172.17.1.1:8301 alive server 1.6.2 2 dc1 <all>
node32 172.17.1.2:8301 alive server 1.6.2 2 dc1 <all>
node33 172.17.1.3:8301 alive server 1.6.2 2 dc1 <all>
node34 172.17.1.4:8301 alive client 1.6.2 2 dc1 <default>
主机 A 的节点只有他们自己机器的节点,主机 B 中的节点全部未注册过来,这是为什么呢?原因就是 consul
绑定的 ip 是容器的内网 ip,主机内部通讯是可以的,跨主机通讯是无法通过内网地址进行通讯的,那么我们怎么做呢?我们通过路由规则进行转发即可,把主机 A 请求主机 B 容器的内网地址转发到主机 B 即可,这里就体现出我们开始给容器分配 ip 的作用了。 我们在主机 A 执行如下命令:
route add -net 172.17.2.0 netmask 255.255.255.0 gw 192.168.236.5
这条命令的意思是,添加一个路由规则 172.17.2.1~172.17.2.254
范围的 ip 请求,全部转发到 192.168.236.5
地址下,也就是我们的主机 B。 同理主机 B 也执行如下命令:
route add -net 172.17.1.0 netmask 255.255.255.0 gw 192.168.236.3
添加完成后,在执行 docker exec -t node_53 consul members
命令:
Node Address Status Type Build Protocol DC Segment
node31 172.17.1.1:8301 alive server 1.6.2 2 dc1 <all>
node32 172.17.1.2:8301 alive server 1.6.2 2 dc1 <all>
node33 172.17.1.3:8301 alive server 1.6.2 2 dc1 <all>
node_51 172.17.2.1:8301 alive server 1.6.2 2 dc1 <all>
node_52 172.17.2.2:8301 alive server 1.6.2 2 dc1 <all>
node_53 172.17.2.3:8301 alive server 1.6.2 2 dc1 <all>
node34 172.17.1.4:8301 alive client 1.6.2 2 dc1 <default>
集群加入就成功了,这就完成了跨主机的 docker 容器加入。 最后给主机 B 部署一个 client
Node8 (client 节点)
docker run -d --name=node_54 --restart=always \
-e ''CONSUL_LOCAL_CONFIG={"leave_on_terminate": true}'' \
-p 8300:8300 \
-p 8301:8301 \
-p 8301:8301/udp \
-p 8302:8302/udp \
-p 8302:8302 \
-p 8400:8400 \
-p 8500:8500 \
-p 8600:8600 \
consul agent -retry-join=172.17.1.1 \
-node-id=$(uuidgen | awk ''{print tolower($0)}'') \
-node=node54 -client 0.0.0.0 -ui
最后的集群节点全部加入成功了,结果如下:
node31 172.17.1.1:8301 alive server 1.6.2 2 dc1 <all>
node32 172.17.1.2:8301 alive server 1.6.2 2 dc1 <all>
node33 172.17.1.3:8301 alive server 1.6.2 2 dc1 <all>
node_51 172.17.2.1:8301 alive server 1.6.2 2 dc1 <all>
node_52 172.17.2.2:8301 alive server 1.6.2 2 dc1 <all>
node_53 172.17.2.3:8301 alive server 1.6.2 2 dc1 <all>
node34 172.17.1.4:8301 alive client 1.6.2 2 dc1 <default>
node54 172.17.2.4:8301 alive client 1.6.2 2 dc1 <default>
执行节点状态命令 docker exec -t node_31 consul operator raft list-peers
:
node32 ee186aef-5f8a-976b-2a33-b20bf79e7da9 172.17.1.2:8300 follower true 3
node33 d86b6b92-19e6-bb00-9437-f988b6dac4b2 172.17.1.3:8300 follower true 3
node31 0ab60093-bed5-be77-f551-6051da7fe790 172.17.1.1:8300 leader true 3
node_51 cfac3b67-fb47-8726-fa31-158516467792 172.17.2.1:8300 follower true 3
node_53 31679abe-923f-0eb7-9709-1ed09980ca9d 172.17.2.3:8300 follower true 3
node_52 207eeb6d-57f2-c65f-0be6-079c402f6afe 172.17.2.2:8300 follower true 3
这样一个包含 6 个 server
+2 个 client
的 consul
容器化集群就部署完成了,我们查看 consul
的 web
面板如下:
应用集成
集群版本的 consul
我们就部署好了,那么我们如何与应用集成呢?我们只要集成集群版本的 consul
注册客户端就行了。 首先加入依赖
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>spring-cloud-starter-consul-cluster</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
第二步在 bootstrap.yml|properties
中指定 spring.cloud.consul.host
为多节点,如下所示:
spring.cloud.consul.host=192.168.23.222:10385,192.168.23.222:10585
如果想输出注册的相关日志的话也可以在 logback 上加上日志配置
<logger name="org.springframework.cloud.consul" level="TRACE"/>
这样配置完成后启动成功就能看到我们的应用注册成功了,下图是我测试的注册成功的效果:
这里显示我的应用节点分别都注册到了集群的 2 个 client
上面,通过 client
的代理转发请求到健康的 server
,从而实现了 consul
的高可用。
总结
这篇文章没有研究什么技术干货,纯粹是工作经验分享,主要讲了 consul
集群部署的方式,传统模式可以通过 HAProxy
来完成集群的部署,但是这种方式的弊端很明显,通过虚拟 ip 还是可能会指向故障的节点,所以我们用 consul
的 client
+server
模式的集群部署,通过 docker
化来充分利用了机器的资源,只需要 2 台机器就能完成集群的高可用效果。
.NET Core+MySql+Nginx 容器化部署
mogr2/auto-orient/strip%7CimageView2/2/w/1240">
上两节我们通过简单的demo学习了docker的基本操作。这一节我们来一个进阶学习,完成ASP.NET Core + MysqL + Nginx的容器化部署。
本文是基于CentOS 7.4环境进行演示,示例项目可以访问进行下载。
同样我们还是以循序渐进的方式来展开。首先来基于Docker来试玩一下MySQL。
//拉取mysql镜像
docker pull mysql
$ docker images$
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/mysql latest 7d83a47ab2d2 13 days ago 408.2 MB
//创建一个mysql实例
$ docker run --name hello.mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e21bbd84e0b5 mysql "docker-entrypoint.sh" 3 minutes ago Up 3 minutes 3306/tcp hello.mysql
//拉取mysql镜像
docker pull mysql
$ docker images$
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/mysql latest 7d83a47ab2d2 13 days ago 408.2 MB
//创建一个mysql实例
$ docker run --name hello.mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e21bbd84e0b5 mysql "docker-entrypoint.sh" 3 minutes ago Up 3 minutes 3306/tcp hello.mysql
下面我们直接在容器中连接到我们刚刚创建的mysql数据库:
$ docker exec -it hello.mysql \ > mysql -uroot -p123456 mysql: [Warning] Using a password on the command line interface can be insecure. Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 8 Server version: 5.7.20 MySQL Community Server (GPL)
Copyright (c) 2000,2017,Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.00 sec)
<h2 id="挂载数据卷">2.2. 挂载数据卷
上面创建的mysql实例其数据都在容器内部存储,这样就暴露了一个问题,如果容器销毁,那么对应的数据库数据就会丢失。那如何持久化存储容器内数据呢?我们可以通过挂载数据卷的方式来解决这一问题。
//创建数据卷
$ docker volume create --name hello.db
hello.db
//查看数据卷信息
$ docker volume inspect hello.db
[
{
"Name": "hello.db","Driver": "local","Mountpoint": "/var/lib/docker/volumes/hello.db/_data","Labels": {},"Scope": "local"
}
]
// 挂载数据卷启动MySql实例
$ docker run --name hello.mysql \
> -v hello.db:/var/lib/mysql \
> -e MYSQL_ROOT_PASSWORD=123456 -d mysql
上面是使用使用了docker volume create
命令创建了一个数据卷,当然我们也可以自行挂载某个目录作为数据卷。
为了演示方便,我准备了一个ASP.NET Core+EFCore+MySql的示例项目。其结构如下所示:
Product实体,并通过ProductsController
暴露WebApi接口。核心代码如下:
Product实体类:
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int StockQty { get; set; }
}
DbContext类:
public class MySqlDbContext : DbContext
{
public MySqlDbContext (DbContextOptions options)
: base(options)
{
}
public DbSet<Product> Products { get; set; }
}
数据库初始化类:
public class DbInitializer
{
public static void Initialize(MySqlDbContext context)
{
context.Database.EnsureCreated();
if (context.Products.Any())
{
return;
}
var products = new Product[]
{
new Product{Name="iphone 6",Price=5000,StockQty=10 },new Product{Name="iphone 7",Price=6000,new Product{Name="iphone 7 plus",Price=7000,new Product{Name="iphone x",Price=8000,StockQty=10 }
};
context.Products.AddRange(products);
context.SaveChanges();
}
}

ProductsController
暴露WebApi接口。核心代码如下:public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int StockQty { get; set; }
}
public class MySqlDbContext : DbContext
{
public MySqlDbContext (DbContextOptions options)
: base(options)
{
}
public DbSet<Product> Products { get; set; }
}
public class DbInitializer
{
public static void Initialize(MySqlDbContext context)
{
context.Database.EnsureCreated();
if (context.Products.Any())
{
return;
}
var products = new Product[]
{
new Product{Name="iphone 6",Price=5000,StockQty=10 },new Product{Name="iphone 7",Price=6000,new Product{Name="iphone 7 plus",Price=7000,new Product{Name="iphone x",Price=8000,StockQty=10 }
};
context.Products.AddRange(products);
context.SaveChanges();
}
}
该数据库初始化类会在项目启动时运行。详细代码可参考。
$ yum install git
$ git --version
git version 1.8.3.1
$ cd ~/demo
$ git clone https://github.com/yanshengjie/Docker.NetCore.
MysqL.git
cloning into 'Docker.NetCore.MysqL'...
remote: Counting objects: 155,done.
remote: Compressing objects: 100% (125/125),done.
remote: Total 155 (delta 42),reused 123 (delta 25),pack-reused 0
Receiving objects: 100% (155/155),534.30 KiB | 333.00 KiB/s,done.
Resolving deltas: 100% (42/42),done.
$ yum install git
$ git --version
git version 1.8.3.1
$ cd ~/demo
$ git clone https://github.com/yanshengjie/Docker.NetCore.
MysqL.git
cloning into 'Docker.NetCore.MysqL'...
remote: Counting objects: 155,done.
remote: Compressing objects: 100% (125/125),done.
remote: Total 155 (delta 42),reused 123 (delta 25),pack-reused 0
Receiving objects: 100% (155/155),534.30 KiB | 333.00 KiB/s,done.
Resolving deltas: 100% (42/42),done.
细心的你会发现,项目中已经定义了Dockerfile,所以我们可以直接使用docker build
构建镜像。
# cd Docker.NetCore.MysqL
[root@iZ288a3qazlZ Docker.NetCore.MysqL]# ls
appsettings.Development.json docker-compose.yml Program.cs Views
appsettings.json Dockerfile proxy.conf wwwroot
bundleconfig.json Docker.NetCore.MysqL.csproj README.md
Controllers LICENSE ScaffoldingReadMe.txt
Data Models Startup.cs
//构建镜像
# docker build -t docker.netcore.MysqL .
Sending build context to Docker daemon 3.045 MB
Step 1 : FROM microsoft/dotnet:latest
---> 7d4dc5c258eb
Step 2 : workdir /app
---> Using cache
---> 98d48a4e278c
Step 3 : copY . /app
---> 6b1bf8bb5261
Removing intermediate container b86460477977
Step 4 : RUN dotnet restore
---> Running in 4e0a46f762bb
Restoring packages for /app/Docker.NetCore.MysqL.csproj...
Installing Microsoft.CodeAnalysis.Razor 2.0.0.
.....
Restore completed in 216.83 ms for /app/Docker.NetCore.MysqL.csproj.
---> 4df70c77916e
Removing intermediate container 4e0a46f762bb
Step 5 : EXPOSE 5000
---> Running in 11b421b3bd3e
---> 3506253060fe
Removing intermediate container 11b421b3bd3e
Step 6 : ENV ASPNETCORE_URLS http://*:5000
---> Running in 201aabbab72c
---> 7f29963a8d96
Removing intermediate container 201aabbab72c
Step 7 : ENTRYPOINT dotnet run
---> Running in c79f73cba162
---> 9d1fb6ee46cb
Removing intermediate container c79f73cba162
Successfully built 9d1fb6ee46cb
[root@iZ288a3qazlZ Docker.NetCore.MysqL]# docker images docker.netcore.MysqL
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.netcore.MysqL latest 9d1fb6ee46cb 13 seconds ago 1.756 GB
数据库
docker提供了--link
参数用于在容器之间建立连接。下面我们实例化创建的镜像docker.netcore.MysqL
并命名容器名为hello.netcore.MysqL
,并使用--link参数与我们文章开头建立的hello.MysqL
容器建立连接。
# docker run --name hello.netcore.MysqL --link hello.MysqL:db -d -p 5000:5000
docker.netcore.MysqL
这里需要特别注意一下
--link=hello.MysqL:db
,这个参数就是告诉Docker容器需要使用hello.MysqL
容器,并将其别名命名为db,这样在hello.netcore.MysqL
这个容器中就可以使用db来作为提供MysqL数据库服务的服务器。这也就是为什么我们.NET Core项目中连接字符串设置为server=db;
的原因。"ConnectionStrings": { "MysqL": "server=db;database=MysqLDbContext;uid=root;pwd=123456;" }
//查看运行中容器列表
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5cbfd27ebe2a docker.netcore.MysqL "dotnet run" 2 minutes ago Up 2 minutes 0.0.0.0:5000->5000/tcp hello.netcore.MysqL
4dfa4159b669 MysqL "docker-entrypoint.sh" About an hour ago Up About an hour 3306/tcp hello.MysqL
//访问api/products
[root@iZ288a3qazlZ Docker.NetCore.MysqL]# curl http://localhost:5000/api/products
[{"productId":1,"name":"iphone 6","price":5000.0000000000000000000000000,"stockQty":10},{"productId":2,"name":"iphone 7","price":6000.0000000000000000000000000,{"productId":3,"name":"iphone 7 plus","price":7000.0000000000000000000000000,{"productId":4,"name":"iphone x","price":8000.000000000000000000000000,"stockQty":10}]
从上图可知,我们完成了.NET Core与MysqL的连接。
Nginx">5. ASP.NET Core + MysqL + Nginx
结合上一篇文章,我们来使用docker-compose完成asp.net core + mysql + nginx的多容器部署。
version: '2'
services:
db:
container_name: hello.db
environment:
MYSQL_ROOT_PASSWORD: 123456
volumes:
- ./mysql:/var/lib/mysql
web:
container_name: hello.web
build: .
depends_on:
- db
links:
- db
reverse-proxy:
container_name: hello.proxy
image: nginx
depends_on:
- web
ports:
- "9090:8080"
volumes:
./proxy.conf:/etc/nginx/conf.d/default.conf
version: '2'
services:
db:
container_name: hello.db
environment:
MYSQL_ROOT_PASSWORD: 123456
volumes:
- ./mysql:/var/lib/mysql
web:
container_name: hello.web
build: .
depends_on:
- db
links:
- db
reverse-proxy:
container_name: hello.proxy
image: nginx
depends_on:
- web
ports:
- "9090:8080"
volumes:
./proxy.conf:/etc/nginx/conf.d/default.conf
其中定义了三个服务:
- db:使用mysql镜像,并挂载当前项目下的mysql文件夹来持久化存储。
- web:基于当前项目构建的容器服务,依赖于db服务。
- reverse-proxy:使用nginx定义反向代理服务,其中挂载了当前项目下的proxy.conf文件作为反向代理配置文件。其中proxy.conf的配置如下(注意proxy_pass指定的url为http://web:5000):
server { listen 8080;
location / { proxy_pass http://web:5000; }
}
<h2 id="启动compose">5.2. 启动Compose
在启动Compose之前,建议清空上面创建的容器。也可以使用docker rm $(docker ps -qa)
清除所有容器。
//启动compose
[root@iZ288a3qazlZ Docker.NetCore.MySql]# docker-compose up -d
Creating network "dockernetcoremysql_default" with the default driver
Building web
Step 1 : FROM microsoft/dotnet:latest
---> 7d4dc5c258eb
Step 2 : WORKDIR /app
---> Using cache
---> 98d48a4e278c
Step 3 : COPY . /app
---> d41b32323c0f
Removing intermediate container 1259f5fb82bc
Step 4 : RUN dotnet restore
---> Running in d482e355de77
Restoring packages for /app/Docker.NetCore.MySql.csproj...
Installing Microsoft.CodeAnalysis.Razor 2.0.0.
.....
Restore completed in 216.83 ms for /app/Docker.NetCore.MySql.csproj.
---> a0658008f161
Removing intermediate container d482e355de77
Step 5 : EXPOSE 5000
---> Running in dc6eeb29fd5e
---> a419314ece08
Removing intermediate container dc6eeb29fd5e
Step 6 : ENV ASPNETCORE_URLS http://*:5000
---> Running in c1d1474b14a0
---> 9cc13c549042
Removing intermediate container c1d1474b14a0
Step 7 : ENTRYPOINT dotnet run
---> Running in efdf0e857a84
---> 830ac11428cf
Removing intermediate container efdf0e857a84
Successfully built 830ac11428cf
Creating hello.db ... done
Creating hello.web ... done
Creating hello.proxy ... done
Creating hello.web ...
Creating hello.proxy ...
[root@iZ288a3qazlZ Docker.NetCore.MySql]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6253bf85682e nginx "nginx -g 'daemon off" 33 seconds ago Up 28 seconds 80/tcp,0.0.0.0:9090->8080/tcp hello.proxy
ea553a9e22f2 dockernetcoremysql_web "dotnet run" 37 seconds ago Up 32 seconds 5000/tcp hello.web
a1f5aa981bfb mysql "docker-entrypoint.sh" 38 seconds ago Up 36 seconds 3306/tcp hello.db
[root@iZ288a3qazlZ Docker.NetCore.MySql]# docker-compose ps
Name Command State Ports
----------------------------------------------------------------------------------
hello.db docker-entrypoint.sh mysqld Up 3306/tcp
hello.proxy nginx -g daemon off; Up 80/tcp,0.0.0.0:9090->8080/tcp
hello.web dotnet run Up 5000/tcp
[root@iZ288a3qazlZ Docker.NetCore.MySql]# curl http://localhost:9090/api/products
[{"productId":1,"stockQty":10}]
上面的运行结果显示,我们已经成功完成了ASP.NET Core+MySql+Nginx的多容器应用部署。通过浏览器访问http:
即可访问我们暴露的api。
我们来验证一下数据库是否成功创建:
[root@iZ288a3qazlZ Docker.NetCore.MySql]# ls mysql auto.cnf client-key.pem ib_logfile0 performance_schema server-key.pem ca-key.pem MySqlDbContext ib_logfile1 private_key.pem sys ca.pem ib_buffer_pool ibtmp1 public_key.pem client-cert.pem ibdata1 mysql server-cert.pem [root@iZ288a3qazlZ Docker.NetCore.MySql]# docker exec -it hello.db mysql -uroot -p123456 mysql: [Warning] Using a password on the command line interface can be insecure. Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 8 Server version: 5.7.20 MySQL Community Server (GPL)
Copyright (c) 2000,Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases;
+-----------------------+
| Database |
+-----------------------+
| information_schema |
| MySqlDbContext |
| mysql |
| performance_schema |
| sys |
+-----------------------+
5 rows in set (0.00 sec)mysql> use MySqlDbContext;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -ADatabase changed
mysql> show tables;
+---------------------------------+
| Tables_in_MySqlDbContext |
+---------------------------------+
| Products |
+---------------------------------+
1 row in set (0.00 sec)
mysql> select * from Products;
+-----------+---------------+-------------------------------------+----------+
| ProductId | Name | Price | StockQty |
+-----------+---------------+-------------------------------------+----------+
| 1 | iphone 6 | 5000.000000000000000000000000000000 | 10 |
| 2 | iphone 7 | 6000.000000000000000000000000000000 | 10 |
| 3 | iphone 7 plus | 7000.000000000000000000000000000000 | 10 |
| 4 | iphone x | 8000.000000000000000000000000000000 | 10 |
+-----------+---------------+-------------------------------------+----------+
4 rows in set (0.00 sec)
从上面的运行结果可知,我们成功将项目文件夹下的mysql文件夹挂载到容器内部进行数据持久化。
本文通过先介绍如何基于Docker实例化MySQL容器,再介绍如何通过挂载数据卷来持久化MySQL数据,以及如何使用--Link参数进行容器之间的连接,完成了.NET Core连接MySQL数据库。 最后,使用Docker-Compose综合ASP.NET Core+MySQL+Nginx完成了容器化部署。
下一节我们来介绍下如何使用Docker-Swarm进行集群部署。
.netcore应用容器化部署
前面,我们讲解了如何在wsl中安装数据库,.netcore运行时,以及如何发布一个.netcore应用,为了构成一个完整的小系列,本节,我们来学习一下,如何将.netcore应用容器化【当然这里还是以wsl为平台】
环境准备
1.Docker安装
之前,出过一个docker的小系列,请参考
(一)零基础小白都能懂的超全Docker入门教程之开篇
(二)Docker Desktop及Kubernetes安装
(三)Docker、k8s使用初体验及Dashboard避坑指南!!!
(四)动手构建第一个自己的docker镜像,并深入学习docker镜像和容器管理
通过以上4个步骤,可以学习到docker的一些基础知识,包括是什么,为什么,怎么用,其中包括,如何构建镜像、启停容器以及一些需要注意的问题。
更多内容请参考相关系列 Docker&k8s_画鸡蛋的不止达芬奇-CSDN博客
2.启用相应功能
因为本节内容是基于wsl的,而且最新版本的docker for windows也是支持wsl2的,所以建议安装wsl2,并在docker中启用wsl,启用如下功能
- 启用WSL 2作为引擎
- 在集成项下,选择相应的系统
如果你尚未安装wsl环境,或非wsl2环境,请参考
wsl安装及版本升级
wsl---ssh远程连接、ip映射及服务自启详细配置
wsl安装mysql初始化数据库并设置服务自启
更多相关内容请参考该系列 linux_画鸡蛋的不止达芬奇-CSDN博客
至此,我们就可以开始本节的正式的内容了。
创建应用
.netcore应用创建
本次,我们创建一个aspnetcore应用,也就是一个web应用,并将其部署到容器中,
首先,我们通过以下命令创建一个web应用并生成
dotnet new web -o DockerTest
dotnet build DockerTest\DockerTest.csproj
然后,再将项目发布到Release路径下,以便再构建镜像的时候应用
dotnet publish DockerTest\DockerTest.csproj -c Release
然后可以,先试运行以下看看效果
dotnet run DockerTest.csproj
或
dotnet DockerTest.dll
Dockerfile创建
在DockerTest.csproj的同级目录下添加Dockerfile文件并且编辑其内容,这里可以使用vs添加该文件,这样会自带默认内容,我们可以通过修改默认内容来达到我们的目标
然后选择目标平台为linux.
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
workdir /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
workdir /src
copY ["DockerTest.csproj", "."]
RUN dotnet restore "./DockerTest.csproj"
copY . .
workdir "/src/."
RUN dotnet build "DockerTest.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "DockerTest.csproj" -c Release -o /app/publish
FROM base AS final
workdir /app
copY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "DockerTest.dll"]
再该文件中,我们可以看到容器暴露的端口为80 443,项目基于aspnet:5.0,
此时,我们可以启用docker来构建镜像。
docker build -t dockertest -f Dockerfile .
# -t指定镜像的名称,这里要注意只能是小写
# -f指定Dockerfile的路径 .表示当前路径
此时,我们只需要等待执行完毕即可。
当镜像构建完毕后,我们可先查看镜像,然后再运行一个容器
docker images #查看所有镜像
docker run -d -p 5060:80 dockertest
#-p表示映射的端口,将本地的5060映射到容器的80端口
docker run -d -p 4446:80 --name 3334 dockertest
#启动特定名称的容器
docker container ls
#查看全部容器
此时,容器即已成功启动,我们可以通过再浏览器中输入地址来验证。
表明我们的容器已经部署成功。
到这里,基本上本节内容已经结束了,但这里提一个坑,不注意的话很容易掉进去,
按照我们的思路,我们如果想将容器内的应用通过特定的端口暴露出来,只需要修改Dockerfile中的EXPOSE即可。
但是,注意了!!!!
如果只更改端口,是没办法通过浏览器访问到的,然后通过各种调试你会发现,只有暴露80端口并添加映射的时候才能访问到,这是因为啥呢???
我这里记录了.netcore应用容器化时更改Expose端口无法访问
其实,这是因为.netcore默认监听的端口是80,因此,容器启动应用时,其实监听的是80端口,要想通过其他端口访问,我们只需要再Dockerfile中添加ENV变量来指定即可
ENV ASPNETCORE_URLS=http://+:5000 #5000是你想用的端口
然后构建出来的镜像,部署到容器后,即可通过这个端口来访问。
好了,本节内容到此结束,有什么疑问请留言,后续将持续更新。
码字不易,还望支持哟!!!
angular应用容器化部署
angular 应用容器化部署
Intro
我自己有做一个个人主页,虽然效果不怎么样(不懂设计的典型程序猿...),但是记录了我对于前端框架及工具的一些实践, 从开始只有一个 angularjs 制作的页面到后面加入 less 动态写css, gulp 自动化的将 less 文件编译成 css 文件以及自动化的压缩 js 和 css,到后面加入的基于 vue 和 angular 实现,主要维护的是基于 angular 的,目前 angular 的个人主页已经支持 PWA(Progressive Web Application),前几天添加了 docker 部署的支持,记录一篇文章记录一下。
个人主页体验地址:https://weihanli.xyz
编写 dockerfile
完整的 dockerfile 如下:
FROM node AS builder
# set working directory
WORKDIR /app
# install and cache app dependencies
COPY . /app
# install dependencies and build the angular app
RUN yarn && yarn run build
FROM nginx:stable-alpine
# copy from dist to nginx root dir
COPY --from=builder /app/dist/weihanli /usr/share/nginx/html
# expose port 80
EXPOSE 80
# set author info
LABEL maintainer="WeihanLi"
# run nginx in foreground
# https://stackoverflow.com/questions/18861300/how-to-run-nginx-within-a-docker-container-without-halting
CMD ["nginx", "-g", "daemon off;"]
整个 dockerfile 可分为两部分,第一部分是编译 angular 应用,生成最后要部署的文件。 第二部分则是将生成的部分拷贝到基于 nginx 的环境中,部署到 nginx 中
打包 docker 镜像
通过 docker build
命令打包 docker 镜像,详细命令使用参考 https://docs.docker.com/engine/reference/commandline/build/
docker build -t weihanli/homepage .
启动容器
docker run
通过 docker run
命令启动一个容器,部署打包好的镜像,详细命令使用参考 https://docs.docker.com/engine/reference/commandline/run/
docker run -p:5200:80 --rm --name homepage-demo weihanli/homepage
docker compose
通过 docker-compose.yml
启动容器,启动命令:docker-compose up
更多 compose 信息参考 https://docs.docker.com/compose/compose-file
docker-compose.yml 文件如下:
version: "3"
services:
web:
image: "weihanli/homepage"
container_name: "weihanli-homepage-demo"
ports:
- "5200:80"
访问容器中的应用
访问 http://localhost:5200 ,即可访问到容器中部署的应用
More
项目源代码:https://github.com/WeihanLi/weihanli.github.io
Contact
Contact me: weihanli@outlook.com
ASP.NET Core 应用程序容器化、持续集成与 Kubernetes 集群部署(一)(转载)
本文结构
- ASP.NET Core 应用程序的构建
- ASP.NET Core 应用程序容器化所需注意的问题
- 应用程序的配置信息
- 端口侦听
- ASP.NET Core 的容器版本
- docker 镜像构建上下文(Build Context)与 Dockerfile 的配套使用
- 前端应用:nginx 的反向代理
- 在容器中运行整个应用程序
- 总结
ASP.NET Core 应用程序的构建
构建 ASP.NET Core 应用程序的方式有很多种,你可以使用 Visual Studio 2017 的项目模板直接创建,也可以在安装了.NET Core SDK 之后,使用 dotnet new 命令创建,具体步骤在此也就不再细表,我仍然使用 Visual Studio 2017 的 ASP.NET Core 项目模板进行创建。在新建项目对话框中,我们可以选择启用 Docker 容器支持,这样的话,Visual Studio 会在新建的 ASP.NET Core 项目中添加 Dockerfile 文件,同时会在解决方案中增加一个 Docker Compose 的项目,用以实现容器编排。然而,我并不太喜欢使用这一功能,虽然它能够带来很多方便,原因主要有二。首先,一个复杂的应用程序解决方案,项目往往不止一个,各项目的运行环境和配置都会有所不同,使用项目模板创建的 Dockerfile 和 Docker Compose 文件有可能还是需要进行修改,甚至重写;其次,我们需要对 IDE 自动生成的代码了如指掌,这样才能理解并在实际项目中正确使用,与其如此,不如自己根据实际需要自己编写,这样可以让自己对整个项目的各个技术细节都有着深刻的理解和认识。
新建 ASP.NET Core 项目之后,就可以开始编写代码来实现我们的业务逻辑了。有关 Visual Studio 2017 开发 ASP.NET Core 应用程序的详细步骤在这里就不多介绍了,作为这次线下活动的演示案例,我开发了一个简单的 App:tasklist,这个 App 使用 Angular 6 作为前端框架,TypeScript 进行前端编程,后端使用 ASP.NET Core Web API 构建,基于 MongoDB 数据库,完整的代码可以在 https://github.com/daxnet/tasklist 找到。该案例项目使用 MIT 许可协议开源。
Tasklist 的业务非常简单,就是允许用户能够增加、删除任务项目,它的界面如下:
在这个界面中,用户可以在文本框中输入需要完成的任务项目,点击 “新增” 按钮可以将任务项目添加到列表,也可以在列表中点击 “删除” 按钮删除指定的项目,文本框下方列出了所有已添加的任务项目。整个后端 ASP.NET Core Web API 解决方案中各项目的依赖关系如下:
具体的代码实现部分就不多介绍了,这里重点介绍一下 ASP.NET Core 应用程序容器化时需要注意的几点问题。
ASP.NET Core 应用程序容器化所需注意的问题
应用程序的配置信息
容器化的应用程序往往都是在容器启动的过程中,将所需的配置信息通过环境变量注入容器,此时运行于容器中的应用就可以读取环境变量来获得运行参数。比如,使用 docker run 命令启动容器时,就可以使用 - e 参数来指定环境变量。因此,理解 ASP.NET Core 应用程序的配置系统是非常重要的,它有助于应用程序配置体系的设计。在《ASP.NET Core 应用程序的参数配置及使用》一文中,我已经简要介绍过 ASP.NET Core 应用程序的配置系统,可供参考。
在此需要注意的一点是,ASP.NET Core 配置系统通常使用冒号(:)来分隔配置数据模型中不同层次的名称。比如,有如下配置数据模型:
"mongo": {
"server": {
"host": "localhost",
"port": 27017
},
"database": "tasklist"
}
如果在 C# 代码中要访问 host,那么就需要使用下面的代码:
var mongoServerHost = Configuration["mongo:server:host"];
然而,如果应用程序需要运行在容器中,这个配置就需要写在容器的编排文件里,比如 docker-compose.yml 文件。但是,有些容器的编排系统,例如 Kubernetes,就不支持在环境变量设置时出现冒号这样的 “非法字符”,为此,ASP.NET Core 的配置也支持使用双下划线分隔。比如:
这样的话,不仅 ASP.NET Core 应用程序在容器中能够获得环境变量的配置,而且诸如 Kubernetes 这样的系统也能在启动容器时,将配置信息设置到环境变量中。
端口侦听
ASP.NET Core 应用程序的端口侦听设置也是一个在容器化过程中非常重要的内容。通常,在开发阶段,我们偏向于在 Main 方法中通过代码的方式指定应用程序所侦听的端口号,比如:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseUrls("http://*:8087")
.UseStartup<Startup>();
UseUrls 支持传入多个参数(因为其参数类型为 params string [] urls),所以我们可以使用 UseUrls 方法给 ASP.NET Core 应用程序绑定多个端口号和协议(http、https):
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseUrls("http://*:8087", "http://*:8088", "https://*:8089")
.UseStartup<Startup>();
在 cmd 中运行 dotnet 命令,启动发布后的 ASP.NET Core 应用程序,结果如下:
但是这种方法缺点是很明显的:我们无法在部署应用程序的时候动态设置需要侦听的端口。对于 ASP.NET Core 应用程序而言,常见的做法有两种:通过命令行参数指定侦听端口,或者使用环境变量。通过命令行参数,只需要在启动应用程序时,指定 --server.urls 参数即可:
通过分号 ";",--server.urls 参数也可以指定多个端口号和协议(http、https):
或者,如果是使用环境变量,只需要配置 ASPNETCORE_URLS 变量即可,如下:
通过分号 ";",ASPNETCORE_URLS 环境变量也可以指定多个端口号和协议(http、https):
因此,事实上我们并不需要在 Main 函数中去显式地指定侦听端口,只需要在最终部署的时候,设置 ASPNETCORE_URLS 环境变量即可。现在,让我们看看 tasklist 代码库中,docker-compose.yml 文件中有关后端服务的环境变量配置:
service:
image: daxnet/tasklist-service
build:
context: service/tasklist
dockerfile: TaskList.Service/Dockerfile
links:
- db
depends_on:
- db
ports:
- 9020:9020
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_URLS=http://*:9020
- mongo__server__host=tasklist-db
- mongo__server__port=27017
- mongo__database=tasklist
container_name: tasklist-service
在上面的配置中:
- ASPNETCORE_ENVIRONMENT:指定 ASP.NET Core 应用程序运行环境,该参数将决定应用程序配置信息的读取方式
- ASPNETCORE_URLS:指定 ASP.NET Core 应用程序的侦听端口
- mongo__server__host:MongoDB 的服务器名称
- mongo__server__port:MongoDB 的侦听端口
- mongo__database:MongoDB 的数据库名称
ASP.NET Core 的容器版本
微软官方发布了.NET Core/ASP.NET Core 的 docker 容器镜像,可以在 https://hub.docker.com/r/microsoft/dotnet/ 中找到。开发人员需要根据不同的场景来选用不同的 tag。比如:
- 2.1-sdk:包含了.NET Core 2.1 SDK
- 2.1-aspnetcore-runtime:包含了 ASP.NET Core 2.1 的运行库
- 2.1-runtime:包含了.NET Core 2.1 的运行库
此外,在这个 repo 下,还有一些预览版的 tag,可以在 https://hub.docker.com/r/microsoft/dotnet/tags/ 页面找到所有的 tag。就 ASP.NET Core 而言,在 2.0(含)之前,需要使用 microsoft/aspnetcore 这个 docker 容器镜像,而从 2.1 开始,则需要使用上面提到的 microsoft/dotnet 这个容器镜像。总之,对于容器镜像和 tag 的选择需要慎重,否则有可能出现一些奇奇怪怪的问题。
docker 镜像构建上下文(Build Context)与 Dockerfile 的配套使用
在上面的 docker-compose.yml 片段中,我们指定了 ASP.NET Core 应用程序的 docker 镜像构建上下文,为 service/tasklist 目录,于是,接下来所有与构建 docker 镜像相关的操作,都会基于这个构建上下文来执行。首先,通过 dockerfile 指定了 Dockerfile 的位置是:service/tasklist/TaskList.Service/Dockerfile(注意这里已经将构建上下文路径带入进来);然后,我们了解一下 Dockerfile 的具体内容:
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app
EXPOSE 9020
FROM microsoft/dotnet:2.1-sdk AS publish
WORKDIR /src
COPY . .
RUN dotnet restore
WORKDIR "/src/TaskList.Service"
RUN dotnet publish "TaskList.Service.csproj" -c Release -o /app
FROM base AS final
WORKDIR /app
COPY --from=publish /app .
CMD ["dotnet", "TaskList.Service.dll"]
这个 Dockerfile 分成三个部分:第一部分指定运行时会采用 microsoft/dotnet:2.1-aspnetcore-runtime 这个 tag,运行目录为 /app 目录,并会向外界暴露 9020 端口;第二部分就是应用程序的编译部分,这里采用 microsoft/dotnet:2.1-sdk 作为编译环境,先设置容器中的工作目录为 /src,然后,将 service/tasklist 目录下的所有内容全部复制到容器中的 /src 目录(注意,虽然 COPY 指令后面是两个点号,但由于我们已经指定了镜像构建上下文,因此,第一个点号就表示 service/tasklist 目录,第二个点号就表示容器中的当前目录,也就是 /src 目录),接着就是标准的 dotnet restore 命令,然后就是进入到 /src/TaskList.Service 目录,执行 dotnet publish 指令,从而编译整个项目,并将编译结果输出到 /app 目录;到了第三部分,将第二部分的输出结果复制到第一部分容器中的 /app 目录(也就是最后那个点号所指定的目录),然后执行 dotnet 命令启动服务。
事实上,如果你在创建 ASP.NET Core 应用程序时,启用了 docker 支持,那么 Visual Studio 会在你的项目中添加一个 Dockerfile,内容与上面的 Dockerfile 类似,不过需要注意的是,使用这个自动生成的 Dockerfile 之前,需要弄清楚镜像构建上下文,否则直接通过 docker build 命令是无法正常完成镜像构建的。
在容器化 ASP.NET Core 应用程序方面,我暂时先介绍这些内容;接下来看看前端部分需要做些什么。
前端应用:nginx 的反向代理
在 tasklist 案例中,前端我采用的是 Angular 6 框架,使用 TypeScript 编写。由于是一个单页面应用,因此,我没有选择相对比较重的 Jetty、Tomcat、IIS 等 Web 容器,而是选择使用了比较轻量的 nginx。当然,前端通过 http 请求访问 ASP.NET Core Web API 应用程序所提供的 RESTful API 接口,那么这里就有一个访问 URL 的问题。使用过 Angular 框架的开发者都知道,通过 environment.ts(或者 environment.prod.ts)代码文件,可以针对不同的运行环境(Development, Staging 或者 Production)来选择设置不同的配置数据,那么,后端服务的 URL 地址又该如何设置呢?
- 使用绝对路径:这不是个好的做法,这就要求将后端 API 的全路径都写死(Hard Code)在 environment.prod.ts 里,显然不是一种合理的做法
- 使用相对路径:这种做法会使得前端 App 调用后端 API 时,产生一个错误的 URL。比如,假设前端运行在 localhost:80,而后端是 localhost:9020,那么如果我们指定 API 的 URL 是相对路径 /api/service,那么当前端程序运行时,它请求的 API 地址就成了 http://localhost/api/service,而不是 http://localhost:9020/api/service
在 tasklist 中,我选择了使用相对路径,然后更改 nginx 的配置,设置了一条反向代理规则:
events {
worker_connections 4096;
}
http {
server {
listen 80;
server_name localhost;
include /etc/nginx/mime.types;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location /api {
proxy_pass http://tasklist-service;
}
}
upstream tasklist-service {
server tasklist-service:9020;
}
}
在这里,当前端页面请求 /api 路径时,nginx 会自动重定向到 http://tasklist-service:9020/api,此时就能正确完成 RESTful API 调用。注意:这里的 tasklist-service 是 ASP.NET Core 应用程序的运行机器名,请参考 docker-compose.yml 文件中 service 配置部分的 container_name 设置。
同样,基于 docker 镜像构建上下文,我们可以使用容器来编译和运行前端代码:
# 基于node 8容器作为编译环境
FROM node:8 AS build
# 首先安装Angular CLI
RUN npm install -g @angular/cli@6.1.5
# 然后将源代码复制到容器中
WORKDIR /src
COPY . .
# 执行npm install以及Angular的编译
RUN npm install
RUN ng build --prod
# 基于nginx容器作为运行环境
FROM nginx AS final
# 将nginx.conf配置文件复制到容器指定目录
COPY nginx.conf /etc/nginx/nginx.conf
# 将Angular编译输出复制到nginx的指定目录
COPY --from=build /src/dist/tasklist /usr/share/nginx/html
在容器中运行整个应用程序
在此,我选择使用 Docker for Windows 来运行整个 tasklist 应用程序。首先启动 Docker for Windows,然后打开 Windows 命令行工具,进入到 tasklist 目录,执行:
docker-compose up --build
经过一段漫长时间的构建过程之后,所有的服务都会启动:
在浏览器中打开我们的应用:
总结
本文为 ASP.NET Core 应用程序容器化、持续集成、持续部署话题的第一部分,重点介绍了 ASP.NET Core 应用程序容器化时需要注意的地方,并展示了整个案例的运行效果。下文会接着讨论基于 Azure DevOps 的持续集成,看看如何使用 Azure DevOps 的服务来完成项目的自动编译。
原文链接
今天的关于Consul 集群版容器化部署与应用集成和容器集群服务的分享已经结束,谢谢您的关注,如果想了解更多关于.NET Core+MySql+Nginx 容器化部署、.netcore应用容器化部署、angular应用容器化部署、ASP.NET Core 应用程序容器化、持续集成与 Kubernetes 集群部署(一)(转载)的相关知识,请在本站进行查询。
本文标签: