[转帖]Docker从入门到动手实践 Docker从入门到动手实践

Docker从入门到动手实践

https://www.cnblogs.com/nsky/p/10853194.html

dockerfile的图很好呢.

但是自己没有做实验 , 其实知识都挺好.

 

docker 入门资料,参考:https://yeasy.gitbooks.io/docker_practice/content/

Dockerfile常用命令,图片来源于网络

Dockerfile 打包控制台应用程序

 新建一个控制台程序,控制台程序添加一个文本文件,去掉.txt 扩展名,改成Dockerfile 输入以下代码

复制代码
FROM microsoft/dotnet:sdk AS build
WORKDIR /code
COPY *.csproj /code
RUN dotnet restore

COPY . /code
RUN dotnet publish -c Release -o out

FROM microsoft/dotnet:runtime
WORKDIR /app
COPY --from=build /code/out /app
ENTRYPOINT ["dotnet","console.dll"]
复制代码

Program.cs 中编写测试代码

 一切准备完成。就是build把项目打包成镜像了

切换到当前项目路径下。输入: docker build -t cn/console:v1 . 

docker build -t :是打包固有的命令

cn/console:v1 :

cn:是组织名称或者说是用户名,如果你想把自己的镜像push到hub.docker 上,cn必须是你自己的用户名

console:是镜像名称

v1:是tag。一个标签,可以用来区分同一个镜像,不同用途。,如果不指定。默认是latest

. :代表当前目录为上下文,dockerfile也必定是在当前目录下

回车后,会看到一系列的执行步骤,dockerfile中。一条命令就是一个步骤

 通过 docker images 可以查看所有镜像

通过docker images cn/console 查看相关镜像

比如我本地有3个 cn/console镜像,但tag不同

既然镜像有了。那么就可以根据镜像生成容器了。容器是镜像的一个实例。镜像运行起来才会有容器,就跟类和对象一样,new一个类,是实例化的操作

 输入命令:

docker run --name myfirst cn/console:v1

因为是占用前端线程运行容器,所有界面无法继续输入命令了。可以Ctrl+c 结束容器运行

 从上面的dockerfile。你会发现,我们是把源码打包成镜像的。也就算执行了restore,到Release操作

其实如果你是已经Release后的文件了。dockerfile可以更简单

FROM microsoft/dotnet
WORKDIR /app
COPY . /app
CMD ["dotnet","run"]

 以上就是一个基础的程序打包成镜像,我觉得这不是重点,常用的应该是应用程序,而不是控制台程序

后面打算把net core api打包成镜像。在讲这个之前,我们先来搭建好环境。

Docker mysql

因为我有个阿里云服务器(CentOS7),然后有2台笔记本,一个是Docker for Windows 环境,一个是CentOS7,所以经常会在这3个环境中来回折腾

两种系统还是有区别的,至少我弄的时候,遇到过不少问题

1:for Windows中默认拉起的镜像都在C盘。会导致C盘越来越大,建议迁移

如果迁移的盘。比如我这个E盘。路径中已经存在MobyLinuxVM.vhdx 。是迁移不过的。要删除,但之前的镜像都没有了

如果你想保存,先重命名MobyLinuxVM.vhdx,迁移后。删除之后的。之前的重命名回来即可

2:共享盘。为了数据卷挂载用

 3:配置镜像加速(https://hlef8lmt.mirror.aliyuncs.com)

 然后可以去hub.docker上寻找需要的镜像,官方的mysql有2个镜像

 当然你通过命令也可以收索到: docker search mysql 

 

 首先来看docker mysql

准备需要挂载的目录和文件,上面我设置的共享盘是D盘,所以挂载的在D盘

my.cnf配置文件,主要是设置mysql的参数

复制代码
[mysqld]
user=mysql
character-set-server=utf8
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
复制代码

data是空的。当run的时候,mysql会写入文件

sql是需要在运行myslq后执行的初始化文件,比如我这里是给刚创建的用户名分配权限

这里为了说明sql是执行成功的。我在加条。创建数据库的sql,创建数据库 docker和user表,并插入一条数据

GRANT ALL PRIVILEGES ON *.* TO 'test'@'%' WITH GRANT OPTION;
Create DATABASE docker;
USE docker;
CREATE TABLE user (ID int auto_increment primary key,name nvarchar(20),address nvarchar(50));
insert into user(name,address)values('刘德华','香港');

初始化后就执行的好处是。不用在run后,去手动执行,关于run后手动执行,

可以查看我之前的docker安装mysql https://www.cnblogs.com/nsky/p/10413136.html

全部配置完成后,开始敲命令,以下命令需要去掉注释

复制代码
docker run -d -p 3306:3306 
--restart always #总是自动重启。比如系统重启,该容器会自动启动
-e MYSQL_USER=test #创建用户名test
-e MYSQL_PASSWORD=123456 #test密码
-e MYSQL_PASSWORD_HOST=% #test 开启外部登陆
-e MYSQL_ROOT_PASSWORD=123456  #root密码
-e MYSQL_ROOT_HOST=% #root开启外部登陆
-v /d/docker/mysql/my.cnf:/etc/my.cnf #配置文件
-v /d/docker/mysql/sql:/docker-entrypoint-initdb.d #初始化的sql
-v /d/docker/mysql/data:/var/lib/mysql  #data文件
--name mysql #镜像名称
mysql #基于那个镜像创建容器
复制代码

执行成功没有异常后。通过  docker ps 可以查看运行的容器,如果没有, 那就通过 docker ps -a 一定会有的

现在可以通过Navicat连接试试

创建了docker库。user表也有数据,能看到mysql库,说明test用户是有权限的

 当我使用mysql-server 镜像时,创建容器会无法启动

可以看到。启动失败后。又继续重启,因为参数指定了restart always

 输入命令  docker logs mysql  查看启动日志

 最后在my.cnf中加这个,经测试,启动成功,就不一一放图了

 数据库准备好了,那么就快速的构建一个net core api 接口

1:引入NugGet包,MySql.Data.EntityFrameworkCore

2:创建DbContext

复制代码
using Docker.Api.Model;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Docker.Api.Data
{
    public class DbUserInfoContext : DbContext
    {
        public DbUserInfoContext(DbContextOptions<DbUserInfoContext> options) : base(options) { }

        public DbSet<UseInfo> userInfos { get; set; }

        /// <summary>
        /// 模型创建时触发
        /// </summary>
        /// <param name="modelBuilder"></param>
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            /*
             修改表名和主键,user对应数据库的表,mysql默认是区分大小写的
             查看:show variables like '%lower%';
            lower_case_table_names 为 0 区分,1 不区分
             */
            modelBuilder.Entity<UseInfo>(b => b.ToTable("user").HasKey(u => u.id));

            //or
            //modelBuilder.Entity<user>()
            //    .ToTable("user")
            //    .HasKey(u => u.id);

            base.OnModelCreating(modelBuilder);
        }
    }
}
复制代码

3:添加UserInfo控制器

复制代码
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading.Tasks;
 5 using Docker.Api.Data;
 6 using Microsoft.AspNetCore.Http;
 7 using Microsoft.AspNetCore.Mvc;
 8 using Microsoft.EntityFrameworkCore;
 9 
10 namespace Docker.Api.Controllers
11 {
12     [Route("api/[controller]")]
13     [ApiController]
14     public class UserInfoController : ControllerBase
15     {
16         private DbUserInfoContext _DbUserInfoContext;
17         public UserInfoController(DbUserInfoContext context)
18         {
19             _DbUserInfoContext = context;
20         }
21         [HttpGet]
22         public async Task<IActionResult> Get()
23         {
24             return new JsonResult(await _DbUserInfoContext.userInfos.FirstOrDefaultAsync());
25         }
26     }
27 }
复制代码

4:配置sql连接字符串: server=localhost;port=3306;userid=test;password=123456;database=docker 

 

run项目。访问能成功获取信息

容器互连,Docker Network

接下来我们把这个api也打包成镜像,然后连接mysql镜像。这称之为容器互连

容器互连有3种方式

1:Link方式。已经被docker淘汰,docker官方不推荐使用该方式

2:Bridger,桥接的方式,单台机器用

3:Overlay 适用于集群时候用

Overlay 就我目前环境不适合测试,集群也不懂。就不搞了

说说LInk和Bridger方式,具体理论知识请看docker官方文档。我这里只实践

现在一切来回忆下

刚上面打包控制台应用程序用的是:microsoft/dotnet 镜像

然后后面带上tag

比如:

Micirosoft/dotnet:sdk

包含了运行时和sdk命令,打包后会很大,因为包含sdk,一般用于测试环境

Microsoft/dotnet:<version>-runtime

包含运行时,不包含sdk,打包后就很小了,一般用于正式环境

Microsoft/dotnet:<version>-runtime-deps

打包的时候,会自包含runtime,也就是部署的机器有没有runtime是没有关系

上面2种,必须机器要包含core环境



修改程序port运行在80上

编写api的Dockerfile

我这里用的sdk,因为要用到sdk命令比如dotnet restore,dotnet publish

如果已经publish的文件,直接用runtime会方便很多。上面也有提及

复制代码
 1 #FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build
 2 FROM microsoft/dotnet:2.2-sdk AS build
 3 WORKDIR /src
 4 WORKDIR /source
 5 #这里的后面的 . 就是/source 路径
 6 #或者 COPY *.csproj /source
 7 COPY *.csproj .
 8 RUN dotnet restore
 9 COPY . .
10 # 发布到 /source/out 下
11 RUN dotnet publish -c Release -o out
12 
13 #FROM mcr.microsoft.com/dotnet/core/runtime:2.2
14 FROM microsoft/dotnet:2.2-aspnetcore-runtime
15 WORKDIR /app
16 COPY --from=build /source/out .
17 EXPOSE 80
18 ENTRYPOINT ["dotnet","Docker.Api.dll"]
复制代码

开始build项目 docker build -t cn/myapi . 

可以看到。这里没有指定tag。所以默认是latest,size也不大

 成功后开始run一个容器,不过这之前要先:

准备挂载目录。因为配置文件 appsettings 会需要动态配置,所以挂载出来

还有,比如一个网站都有log日志,这些也需要挂载出来。便于管理。

我这里就只挂载appsettings.json

 执行命令:

docker run -d -p 80:80 --restart always --link mysql:mysqldb -v /d/docker/myapi/appsettings.json:/app/appsettings.json --name api cn/myapi

 分析:

--restart always :总是重启

-d:是在后台执行

-p 80:80 :第一个80是暴露给外部的。第二个80是程序的。

--link mysql:mysqldb : mysql是容器名称,mysqldb是自定义名称,可以理解为服务器

-v /d/docker/myapi/appsettings.json:/app/appsettings.json:这里就是挂载外部的数据卷了

也许你会问。我怎么知道这个路径的:/app/appsettings.json。从编写的dockerfile能分析出来,待会也可以进入容器看看

最有的工作目录是 根路径下: /app

然后通过页面访问试试

 发现依然无法访问,因为修改appsettings.json的连接方式

记住这里是修改D:\docker\myapi\appsettings.json ,因为已经挂载出来

把server改成mysqldb,然后重启容器: docker restart api 

再次刷新页面

 我们 进入容器看看: docker exec -it api bash 可以看到根目录下存在app目录

 

进入app目录

个人认为link方式是最简单的。在这3种中,接下来看看Bridge方式

1:首先创建一个网络 network,名称叫api2bridge

 docker network create -d bridge api2bridge 

通过: docker network ls 可以查看到已经创建成功

2:实例化容器

为了区别于上面的80端口,这里新增一个8081

 docker run -d -p 8081:80 --restart always  -v /d/docker/myapi/appsettings.json:/app/appsettings.json --net api2bridge --name api2 cn/myapi

 创建容器的时候,自定network 这里的--net api2bridge 就是上面的bridge

3:连接2个容器,通过: docker network connect api2bridge mysql  把api2和mysql连接起来

 4:修改appsettings.json  server=mysql 

 5 : restart 容器,如果是在创建容器前修改的配置文件。是不需要重启的,测试通过

 

 看看这两个容器是怎么连接的。通过命令: docker inspect api2bridge 可以查看对象的元数据(容器或者网络)

 

 分别看看;

docker inspect api2

 

 

docker inspect mysql

 

 你会发现mysql有个"IPAddress":地址,

 上面我们在api2中的appsettings.json的server是直接些的容器名称:mysql。也可以直接些这个ip地址。比如: server=172.20.0.3 同样是可以的。

 Overlay方式就不讲了。因为我也不知道。哈哈

docker-compose 容器编排

通过这几个例子你会发现。2个容器要部署2个,如果项目依赖mysql,redis,MQ等等。那得部署多次,如此重复性的工作会影响效率

所以有了docker-compose,compose

参考:https://yeasy.gitbooks.io/docker_practice/content/compose/install.html

安装:

sudo curl -L https://github.com/docker/compose/releases/download/1.17.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

安装完成后,可以通过: docker-compose --version 查看版本

 通过: docker-compose --help 查看基本的命令

 不过我英文不好,就通过百度翻译了,翻译得有点生硬。仅供参考

复制代码
Commands:
  build              建立或重建服务
  bundle             从撰写文件生成Docker捆绑包
  config             验证并查看撰写文件
  create             创建服务
  down               停止并删除容器、网络、图像和卷
  events             从容器接收实时事件
  exec               在正在运行的容器中执行命令
  help               获取有关命令的帮助
  images             列表图像
  kill               杀死容器
  logs               查看容器的输出
  pause              暂停服务
  port               打印端口绑定的公共端口
  ps                 列表容器
  pull               拉取服务图像
  push               推送服务图像
  restart            重新启动服务
  rm                 移除停止的容器
  run                运行一次性命令
  scale              设置服务的容器数
  start              启动服务
  stop               停止服务
  top                显示正在运行的进程
  unpause            取消暂停服务
  up                 创建和启动容器
  version            显示Docker撰写版本信息
复制代码

目前为止已经有3个容器了,

为了区别于之前的mysql和api和api2,这里命名要修改,编写在程序根目录下添加docker-compose.yml文件

compose用的是yml语法。可以参考阮一峰些的文章

http://www.ruanyifeng.com/blog/2016/07/yaml.html

项目准备。依然在上面的api项目中添砖加瓦

还记得上面初始化的创建docker库,user表吗。这里我们通过在代码中来实现,

场景:创建myslq的时候,判断数据库是否有数据,否则新增一条数据

技术栈:项目依赖mysql,redis,其实我工作中用的都是mssql,所以待会也会介绍

 1:init.sql 只保留一条sql语句

 2:新增UserInit类。用于初始化数据

复制代码
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Docker.Api.Data
{
    public class UserInit
    {
        private ILogger<UserInit> _logger;

        public UserInit(ILogger<UserInit> logger)
        {
            _logger = logger;
        }

        public static async Task InitData(IApplicationBuilder app, ILoggerFactory loggerFactory)
        {

            using (var scope = app.ApplicationServices.CreateScope())
            {
                var context = scope.ServiceProvider.GetService<DbUserInfoContext>();
                var logger = scope.ServiceProvider.GetService<ILogger<UserInit>>();
                logger.LogDebug("begin mysql init");
                context.Database.Migrate();
                if (context.userInfos.Count() <= 0)
                {
                    context.userInfos.Add(new Model.UseInfo
                    {
                        name = "admin",
                        address = "博客园"
                    });
                    context.SaveChanges();
                }
            }
            await Task.CompletedTask;
        }
    }
}
复制代码

程序启动调用:

3:实体类

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Docker.Api.Model
{
    public class UseInfo
    {
        public int id { get; set; }
        public string name { get; set; }
        public string address { get; set; }
    }
}
复制代码

4:DbContext 上面也列出,这里就不展示了

5:RedisHelper网络有。这里也不提了

只准备2个接口。用于测试redis。一个读,一个写

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace Docker.Api.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class RedisController : ControllerBase
    {
        [HttpPost]
        public void Post()
        {
            RedisCommon.GetRedis().StringSet("docker", "hello", TimeSpan.FromMinutes(1));
        }
        [HttpGet]
        public string Get()
        {
            var docker = RedisCommon.GetRedis().GetStringKey("docker");
            if (docker.HasValue) return docker.ToString();
            return "empty";
        }
    }
}
复制代码

6:根据Model生成Migration,这里简单过一下,具体参考我之前的:https://www.cnblogs.com/nsky/p/10323415.html

调出程序包管理控制台

 输入: Add-Migration init 

 如果成功了就会这样:

 编写docker-compose.yml 文件,我这里的注释是便于理解。尽量不要写

注:我是直接在项目中创建的文本文件,然后修改后缀名

在网络上看到说。如果是在外部创建的记事本。要修改编码为:ASCII编码格式,我未测试

复制代码
version: '3'

services:
  db:
    image: mysql
    container_name: 'mysql01'
    command: --character-set-server=utf8 --collation-server=utf8_general_ci
    restart: always
    ports:
      - '3307:3306'
    environment:
      MYSQL_USER: test
      MYSQL_PASSWORD: 123456
      MYSQL_PASSWORD_HOST: '%'
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_ROOT_HOST: '%'
    volumes:
      - /d/docker/mysql02/my.cnf:/etc/my.cnf
      - /d/docker/mysql02/data:/var/lib/mysql
      - /d/docker/mysql02/SqlInit:/docker-entrypoint-initdb.d
  redis:
    image: redis
    container_name: 'redis'
    command: redis-server /usr/local/etc/redis/redis.conf 
    restart: always
    ports:
      - '6379:6379'
    environment:
      requirepass: 123456 #redis密码
      appendonly: 'yes' #redis是否持久化
    volumes:
      - /d/docker/redis/conf/redis.conf:/usr/local/etc/redis/redis.conf
      - /d/docker/redis/data:/data #这里会保存持久化数据
  web:
    build: . #会执行当前目录下面的dockerfile文件
    container_name: 'api3' #容器名称
    restart: always # web依赖于db,如果web比db启动快。就连接不上db导致web异常,web容器启动失败,restart可以不断重试,直到连接为止
    volumes:
      - /d/docker/myapi/appsettings.json:/app/appsettings.json
    ports:
      - '8082:80'
    depends_on: #依赖db容器,并不代表执行顺序
      - db
      - redis
复制代码

如果想看编写的yml文件是否正确,可以去在线的网站,验证是否正确,比如:http://nodeca.github.io/js-yaml/

 切换到当前目录输入: docker-compose build 会开始build项目成镜像

 查看镜像:名字叫dockerapi_web

 输入命令: docker-compose up 会开始创建容器并启动

 

输出的日志太多。这里只点几个有用的看

EFcore插入Migration历史记录

 创建表:

 直到最后,程序阻塞。显示成功,因为这里没用用 -d 会阻塞,调试的时候不建议 -d

 然后新打开一个PowerShell,输入docker ps 查看运行的容器

 分别测试是否成功

 

 同样验证redis,用RedisDesktopManager连接

 

从容器可以看出api3端口是8082,尝试访问下

 测试写redis,打开Postman写入Redis

 写人成功

那么读取就不是什么大问题了

问题汇总:

如果你修改了代码,需要重新build。那么先删除容器: docker-compose down 会停止容器并删除

 docker-compose ps 查看容器列表

  docker-compose up -d  后端运行,不阻塞前端

  docker-compose restart  重启所有容器。

 

 自此所有容器成功运行,但我感觉还不够,因为一直都是在windos上玩。而没有上CentOS7,可我又不缺CentOS环境。所以要玩一把

net core Api 跨平台部署

技术栈:Jexus,mysql,mssql,redis

关于jexus部署net core 可以参考我前面写的文章:https://www.cnblogs.com/nsky/p/10386460.html

既然要加入新的成员。jexus 和 mssql,那么就得修改docker-compose文件

 在通过docker-compose统一打包前,我们先来单独玩玩mssql

准备数据卷挂载目录

 data:保存数据库文件

sql:执行的脚本。mssql没有mysql的docker-entrypoint-initdb.d 挂载,启动mysql就执行sql。这里sql文件夹

虽然保存的是.sql文件。但要手动执行,不知道是不是我没有找到具体的方案

sql里面放一个init.sql文件。编写sql脚本如下

 这里要注意一点,一条语句完成必须要带一个Go语句

参考官方文档:

https://docs.microsoft.com/zh-cn/sql/linux/quickstart-install-connect-docker?view=sql-server-2017&pivots=cs1-bash

https://docs.microsoft.com/zh-cn/sql/linux/tutorial-restore-backup-in-sql-server-container?view=sql-server-2017

镜像文档:https://hub.docker.com/_/microsoft-mssql-server

复制代码
//注释部分
docker run -d -p 1433:1433 \
-e ACCEPT_EULA=Y \ #确认您接受最终用户许可协议。
-e SA_PASSWORD=DockerPwd123 \ #强大的系统管理员(SA)密码:至少8个字符,包括大写,小写字母,基数为10的数字和/或非字母数字符号。
-e MSSQL_PID=Express \ #版本(Developer,Express,Enterprise,EnterpriseCore)默认值:Developer  
-v /docker/mssql:/var/opt/mssql \  # 映射数据库
v /d/docker/mssql/sql:/script #把需要执行的脚本放这里,script路径随便改,不是初始化执行,是手到执行
--name mssql #容器名称
mcr.microsoft.com/mssql/server #镜像
复制代码

执行成功后。数据卷挂载目录。生成了文件

此时data也有默认的数据库了

 通过 MSSMS(  Microsoft SQL Server Management Studio )连接试试

 刚上面说了sql中文件是没有被执行的。必须手动执行。

手动执行前,先来看看其他一些相关命令

进入容器后: docker exec -it mssql bash 

登陆数据库:localhost也可以用指定的ip代替,如果有端口。则带端口号即可

/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P DockerPwd123

-S 是服务器,不管端口是多少,都不用写
-U 是用户名 
-P 是密码

如果出现 1> 说明的登陆成功了

可以输入语句:select getdate() 试试,回车后,需要加Go语句,不过日期怎么不对?好像是相差8个时区

 执行sql中的文件

登陆容器后执行操作: /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P DockerPwd123 -i /script/init.sql 

 

挂载目录也有,这样就算容器无法进入。数据库也存在

由于时间问题,docker-compose 就不加入mssql,只加jexus,修改docker-compose如下

阿里云安装docker-compose 特别慢,今天就不写。后期在加上

关键时刻掉链子,写了这么多。其实也就一点皮毛而已,docker强大之处远远不止这些

未完待续

Portainer管理镜像

上传镜像到hub.docker

源码:https://github.com/byniqing/docker-compose

docker 入门资料,参考:https://yeasy.gitbooks.io/docker_practice/content/

Dockerfile常用命令,图片来源于网络

Dockerfile 打包控制台应用程序

 新建一个控制台程序,控制台程序添加一个文本文件,去掉.txt 扩展名,改成Dockerfile 输入以下代码

复制代码
FROM microsoft/dotnet:sdk AS build
WORKDIR /code
COPY *.csproj /code
RUN dotnet restore

COPY . /code
RUN dotnet publish -c Release -o out

FROM microsoft/dotnet:runtime
WORKDIR /app
COPY --from=build /code/out /app
ENTRYPOINT ["dotnet","console.dll"]
复制代码

Program.cs 中编写测试代码

 一切准备完成。就是build把项目打包成镜像了

切换到当前项目路径下。输入: docker build -t cn/console:v1 . 

docker build -t :是打包固有的命令

cn/console:v1 :

cn:是组织名称或者说是用户名,如果你想把自己的镜像push到hub.docker 上,cn必须是你自己的用户名

console:是镜像名称

v1:是tag。一个标签,可以用来区分同一个镜像,不同用途。,如果不指定。默认是latest

. :代表当前目录为上下文,dockerfile也必定是在当前目录下

回车后,会看到一系列的执行步骤,dockerfile中。一条命令就是一个步骤

 通过 docker images 可以查看所有镜像

通过docker images cn/console 查看相关镜像

比如我本地有3个 cn/console镜像,但tag不同

既然镜像有了。那么就可以根据镜像生成容器了。容器是镜像的一个实例。镜像运行起来才会有容器,就跟类和对象一样,new一个类,是实例化的操作

 输入命令:

docker run --name myfirst cn/console:v1

因为是占用前端线程运行容器,所有界面无法继续输入命令了。可以Ctrl+c 结束容器运行

 从上面的dockerfile。你会发现,我们是把源码打包成镜像的。也就算执行了restore,到Release操作

其实如果你是已经Release后的文件了。dockerfile可以更简单

FROM microsoft/dotnet
WORKDIR /app
COPY . /app
CMD ["dotnet","run"]

 以上就是一个基础的程序打包成镜像,我觉得这不是重点,常用的应该是应用程序,而不是控制台程序

后面打算把net core api打包成镜像。在讲这个之前,我们先来搭建好环境。

Docker mysql

因为我有个阿里云服务器(CentOS7),然后有2台笔记本,一个是Docker for Windows 环境,一个是CentOS7,所以经常会在这3个环境中来回折腾

两种系统还是有区别的,至少我弄的时候,遇到过不少问题

1:for Windows中默认拉起的镜像都在C盘。会导致C盘越来越大,建议迁移

如果迁移的盘。比如我这个E盘。路径中已经存在MobyLinuxVM.vhdx 。是迁移不过的。要删除,但之前的镜像都没有了

如果你想保存,先重命名MobyLinuxVM.vhdx,迁移后。删除之后的。之前的重命名回来即可

2:共享盘。为了数据卷挂载用

 3:配置镜像加速(https://hlef8lmt.mirror.aliyuncs.com)

 然后可以去hub.docker上寻找需要的镜像,官方的mysql有2个镜像

 当然你通过命令也可以收索到: docker search mysql 

 

 首先来看docker mysql

准备需要挂载的目录和文件,上面我设置的共享盘是D盘,所以挂载的在D盘

my.cnf配置文件,主要是设置mysql的参数

复制代码
[mysqld]
user=mysql
character-set-server=utf8
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
复制代码

data是空的。当run的时候,mysql会写入文件

sql是需要在运行myslq后执行的初始化文件,比如我这里是给刚创建的用户名分配权限

这里为了说明sql是执行成功的。我在加条。创建数据库的sql,创建数据库 docker和user表,并插入一条数据

GRANT ALL PRIVILEGES ON *.* TO 'test'@'%' WITH GRANT OPTION;
Create DATABASE docker;
USE docker;
CREATE TABLE user (ID int auto_increment primary key,name nvarchar(20),address nvarchar(50));
insert into user(name,address)values('刘德华','香港');

初始化后就执行的好处是。不用在run后,去手动执行,关于run后手动执行,

可以查看我之前的docker安装mysql https://www.cnblogs.com/nsky/p/10413136.html

全部配置完成后,开始敲命令,以下命令需要去掉注释

复制代码
docker run -d -p 3306:3306 
--restart always #总是自动重启。比如系统重启,该容器会自动启动
-e MYSQL_USER=test #创建用户名test
-e MYSQL_PASSWORD=123456 #test密码
-e MYSQL_PASSWORD_HOST=% #test 开启外部登陆
-e MYSQL_ROOT_PASSWORD=123456  #root密码
-e MYSQL_ROOT_HOST=% #root开启外部登陆
-v /d/docker/mysql/my.cnf:/etc/my.cnf #配置文件
-v /d/docker/mysql/sql:/docker-entrypoint-initdb.d #初始化的sql
-v /d/docker/mysql/data:/var/lib/mysql  #data文件
--name mysql #镜像名称
mysql #基于那个镜像创建容器
复制代码

执行成功没有异常后。通过  docker ps 可以查看运行的容器,如果没有, 那就通过 docker ps -a 一定会有的

现在可以通过Navicat连接试试

创建了docker库。user表也有数据,能看到mysql库,说明test用户是有权限的

 当我使用mysql-server 镜像时,创建容器会无法启动

可以看到。启动失败后。又继续重启,因为参数指定了restart always

 输入命令  docker logs mysql  查看启动日志

 最后在my.cnf中加这个,经测试,启动成功,就不一一放图了

 数据库准备好了,那么就快速的构建一个net core api 接口

1:引入NugGet包,MySql.Data.EntityFrameworkCore

2:创建DbContext

复制代码
using Docker.Api.Model;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Docker.Api.Data
{
    public class DbUserInfoContext : DbContext
    {
        public DbUserInfoContext(DbContextOptions<DbUserInfoContext> options) : base(options) { }

        public DbSet<UseInfo> userInfos { get; set; }

        /// <summary>
        /// 模型创建时触发
        /// </summary>
        /// <param name="modelBuilder"></param>
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            /*
             修改表名和主键,user对应数据库的表,mysql默认是区分大小写的
             查看:show variables like '%lower%';
            lower_case_table_names 为 0 区分,1 不区分
             */
            modelBuilder.Entity<UseInfo>(b => b.ToTable("user").HasKey(u => u.id));

            //or
            //modelBuilder.Entity<user>()
            //    .ToTable("user")
            //    .HasKey(u => u.id);

            base.OnModelCreating(modelBuilder);
        }
    }
}
复制代码

3:添加UserInfo控制器

复制代码
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading.Tasks;
 5 using Docker.Api.Data;
 6 using Microsoft.AspNetCore.Http;
 7 using Microsoft.AspNetCore.Mvc;
 8 using Microsoft.EntityFrameworkCore;
 9 
10 namespace Docker.Api.Controllers
11 {
12     [Route("api/[controller]")]
13     [ApiController]
14     public class UserInfoController : ControllerBase
15     {
16         private DbUserInfoContext _DbUserInfoContext;
17         public UserInfoController(DbUserInfoContext context)
18         {
19             _DbUserInfoContext = context;
20         }
21         [HttpGet]
22         public async Task<IActionResult> Get()
23         {
24             return new JsonResult(await _DbUserInfoContext.userInfos.FirstOrDefaultAsync());
25         }
26     }
27 }
复制代码

4:配置sql连接字符串: server=localhost;port=3306;userid=test;password=123456;database=docker 

 

run项目。访问能成功获取信息

容器互连,Docker Network

接下来我们把这个api也打包成镜像,然后连接mysql镜像。这称之为容器互连

容器互连有3种方式

1:Link方式。已经被docker淘汰,docker官方不推荐使用该方式

2:Bridger,桥接的方式,单台机器用

3:Overlay 适用于集群时候用

Overlay 就我目前环境不适合测试,集群也不懂。就不搞了

说说LInk和Bridger方式,具体理论知识请看docker官方文档。我这里只实践

现在一切来回忆下

刚上面打包控制台应用程序用的是:microsoft/dotnet 镜像

然后后面带上tag

比如:

Micirosoft/dotnet:sdk

包含了运行时和sdk命令,打包后会很大,因为包含sdk,一般用于测试环境

Microsoft/dotnet:<version>-runtime

包含运行时,不包含sdk,打包后就很小了,一般用于正式环境

Microsoft/dotnet:<version>-runtime-deps

打包的时候,会自包含runtime,也就是部署的机器有没有runtime是没有关系

上面2种,必须机器要包含core环境



修改程序port运行在80上

编写api的Dockerfile

我这里用的sdk,因为要用到sdk命令比如dotnet restore,dotnet publish

如果已经publish的文件,直接用runtime会方便很多。上面也有提及

复制代码
 1 #FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build
 2 FROM microsoft/dotnet:2.2-sdk AS build
 3 WORKDIR /src
 4 WORKDIR /source
 5 #这里的后面的 . 就是/source 路径
 6 #或者 COPY *.csproj /source
 7 COPY *.csproj .
 8 RUN dotnet restore
 9 COPY . .
10 # 发布到 /source/out 下
11 RUN dotnet publish -c Release -o out
12 
13 #FROM mcr.microsoft.com/dotnet/core/runtime:2.2
14 FROM microsoft/dotnet:2.2-aspnetcore-runtime
15 WORKDIR /app
16 COPY --from=build /source/out .
17 EXPOSE 80
18 ENTRYPOINT ["dotnet","Docker.Api.dll"]
复制代码

开始build项目 docker build -t cn/myapi . 

可以看到。这里没有指定tag。所以默认是latest,size也不大

 成功后开始run一个容器,不过这之前要先:

准备挂载目录。因为配置文件 appsettings 会需要动态配置,所以挂载出来

还有,比如一个网站都有log日志,这些也需要挂载出来。便于管理。

我这里就只挂载appsettings.json

 执行命令:

docker run -d -p 80:80 --restart always --link mysql:mysqldb -v /d/docker/myapi/appsettings.json:/app/appsettings.json --name api cn/myapi

 分析:

--restart always :总是重启

-d:是在后台执行

-p 80:80 :第一个80是暴露给外部的。第二个80是程序的。

--link mysql:mysqldb : mysql是容器名称,mysqldb是自定义名称,可以理解为服务器

-v /d/docker/myapi/appsettings.json:/app/appsettings.json:这里就是挂载外部的数据卷了

也许你会问。我怎么知道这个路径的:/app/appsettings.json。从编写的dockerfile能分析出来,待会也可以进入容器看看

最有的工作目录是 根路径下: /app

然后通过页面访问试试

 发现依然无法访问,因为修改appsettings.json的连接方式

记住这里是修改D:\docker\myapi\appsettings.json ,因为已经挂载出来

把server改成mysqldb,然后重启容器: docker restart api 

再次刷新页面

 我们 进入容器看看: docker exec -it api bash 可以看到根目录下存在app目录

 

进入app目录

个人认为link方式是最简单的。在这3种中,接下来看看Bridge方式

1:首先创建一个网络 network,名称叫api2bridge

 docker network create -d bridge api2bridge 

通过: docker network ls 可以查看到已经创建成功

2:实例化容器

为了区别于上面的80端口,这里新增一个8081

 docker run -d -p 8081:80 --restart always  -v /d/docker/myapi/appsettings.json:/app/appsettings.json --net api2bridge --name api2 cn/myapi

 创建容器的时候,自定network 这里的--net api2bridge 就是上面的bridge

3:连接2个容器,通过: docker network connect api2bridge mysql  把api2和mysql连接起来

 4:修改appsettings.json  server=mysql 

 5 : restart 容器,如果是在创建容器前修改的配置文件。是不需要重启的,测试通过

 

 看看这两个容器是怎么连接的。通过命令: docker inspect api2bridge 可以查看对象的元数据(容器或者网络)

 

 分别看看;

docker inspect api2

 

 

docker inspect mysql

 

 你会发现mysql有个"IPAddress":地址,

 上面我们在api2中的appsettings.json的server是直接些的容器名称:mysql。也可以直接些这个ip地址。比如: server=172.20.0.3 同样是可以的。

 Overlay方式就不讲了。因为我也不知道。哈哈

docker-compose 容器编排

通过这几个例子你会发现。2个容器要部署2个,如果项目依赖mysql,redis,MQ等等。那得部署多次,如此重复性的工作会影响效率

所以有了docker-compose,compose

参考:https://yeasy.gitbooks.io/docker_practice/content/compose/install.html

安装:

sudo curl -L https://github.com/docker/compose/releases/download/1.17.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

安装完成后,可以通过: docker-compose --version 查看版本

 通过: docker-compose --help 查看基本的命令

 不过我英文不好,就通过百度翻译了,翻译得有点生硬。仅供参考

复制代码
Commands:
  build              建立或重建服务
  bundle             从撰写文件生成Docker捆绑包
  config             验证并查看撰写文件
  create             创建服务
  down               停止并删除容器、网络、图像和卷
  events             从容器接收实时事件
  exec               在正在运行的容器中执行命令
  help               获取有关命令的帮助
  images             列表图像
  kill               杀死容器
  logs               查看容器的输出
  pause              暂停服务
  port               打印端口绑定的公共端口
  ps                 列表容器
  pull               拉取服务图像
  push               推送服务图像
  restart            重新启动服务
  rm                 移除停止的容器
  run                运行一次性命令
  scale              设置服务的容器数
  start              启动服务
  stop               停止服务
  top                显示正在运行的进程
  unpause            取消暂停服务
  up                 创建和启动容器
  version            显示Docker撰写版本信息
复制代码

目前为止已经有3个容器了,

为了区别于之前的mysql和api和api2,这里命名要修改,编写在程序根目录下添加docker-compose.yml文件

compose用的是yml语法。可以参考阮一峰些的文章

http://www.ruanyifeng.com/blog/2016/07/yaml.html

项目准备。依然在上面的api项目中添砖加瓦

还记得上面初始化的创建docker库,user表吗。这里我们通过在代码中来实现,

场景:创建myslq的时候,判断数据库是否有数据,否则新增一条数据

技术栈:项目依赖mysql,redis,其实我工作中用的都是mssql,所以待会也会介绍

 1:init.sql 只保留一条sql语句

 2:新增UserInit类。用于初始化数据

复制代码
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Docker.Api.Data
{
    public class UserInit
    {
        private ILogger<UserInit> _logger;

        public UserInit(ILogger<UserInit> logger)
        {
            _logger = logger;
        }

        public static async Task InitData(IApplicationBuilder app, ILoggerFactory loggerFactory)
        {

            using (var scope = app.ApplicationServices.CreateScope())
            {
                var context = scope.ServiceProvider.GetService<DbUserInfoContext>();
                var logger = scope.ServiceProvider.GetService<ILogger<UserInit>>();
                logger.LogDebug("begin mysql init");
                context.Database.Migrate();
                if (context.userInfos.Count() <= 0)
                {
                    context.userInfos.Add(new Model.UseInfo
                    {
                        name = "admin",
                        address = "博客园"
                    });
                    context.SaveChanges();
                }
            }
            await Task.CompletedTask;
        }
    }
}
复制代码

程序启动调用:

3:实体类

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Docker.Api.Model
{
    public class UseInfo
    {
        public int id { get; set; }
        public string name { get; set; }
        public string address { get; set; }
    }
}
复制代码

4:DbContext 上面也列出,这里就不展示了

5:RedisHelper网络有。这里也不提了

只准备2个接口。用于测试redis。一个读,一个写

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace Docker.Api.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class RedisController : ControllerBase
    {
        [HttpPost]
        public void Post()
        {
            RedisCommon.GetRedis().StringSet("docker", "hello", TimeSpan.FromMinutes(1));
        }
        [HttpGet]
        public string Get()
        {
            var docker = RedisCommon.GetRedis().GetStringKey("docker");
            if (docker.HasValue) return docker.ToString();
            return "empty";
        }
    }
}
复制代码

6:根据Model生成Migration,这里简单过一下,具体参考我之前的:https://www.cnblogs.com/nsky/p/10323415.html

调出程序包管理控制台

 输入: Add-Migration init 

 如果成功了就会这样:

 编写docker-compose.yml 文件,我这里的注释是便于理解。尽量不要写

注:我是直接在项目中创建的文本文件,然后修改后缀名

在网络上看到说。如果是在外部创建的记事本。要修改编码为:ASCII编码格式,我未测试

复制代码
version: '3'

services:
  db:
    image: mysql
    container_name: 'mysql01'
    command: --character-set-server=utf8 --collation-server=utf8_general_ci
    restart: always
    ports:
      - '3307:3306'
    environment:
      MYSQL_USER: test
      MYSQL_PASSWORD: 123456
      MYSQL_PASSWORD_HOST: '%'
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_ROOT_HOST: '%'
    volumes:
      - /d/docker/mysql02/my.cnf:/etc/my.cnf
      - /d/docker/mysql02/data:/var/lib/mysql
      - /d/docker/mysql02/SqlInit:/docker-entrypoint-initdb.d
  redis:
    image: redis
    container_name: 'redis'
    command: redis-server /usr/local/etc/redis/redis.conf 
    restart: always
    ports:
      - '6379:6379'
    environment:
      requirepass: 123456 #redis密码
      appendonly: 'yes' #redis是否持久化
    volumes:
      - /d/docker/redis/conf/redis.conf:/usr/local/etc/redis/redis.conf
      - /d/docker/redis/data:/data #这里会保存持久化数据
  web:
    build: . #会执行当前目录下面的dockerfile文件
    container_name: 'api3' #容器名称
    restart: always # web依赖于db,如果web比db启动快。就连接不上db导致web异常,web容器启动失败,restart可以不断重试,直到连接为止
    volumes:
      - /d/docker/myapi/appsettings.json:/app/appsettings.json
    ports:
      - '8082:80'
    depends_on: #依赖db容器,并不代表执行顺序
      - db
      - redis
复制代码

如果想看编写的yml文件是否正确,可以去在线的网站,验证是否正确,比如:http://nodeca.github.io/js-yaml/

 切换到当前目录输入: docker-compose build 会开始build项目成镜像

 查看镜像:名字叫dockerapi_web

 输入命令: docker-compose up 会开始创建容器并启动

 

输出的日志太多。这里只点几个有用的看

EFcore插入Migration历史记录

 创建表:

 直到最后,程序阻塞。显示成功,因为这里没用用 -d 会阻塞,调试的时候不建议 -d

 然后新打开一个PowerShell,输入docker ps 查看运行的容器

 分别测试是否成功

 

 同样验证redis,用RedisDesktopManager连接

 

从容器可以看出api3端口是8082,尝试访问下

 测试写redis,打开Postman写入Redis

 写人成功

那么读取就不是什么大问题了

问题汇总:

如果你修改了代码,需要重新build。那么先删除容器: docker-compose down 会停止容器并删除

 docker-compose ps 查看容器列表

  docker-compose up -d  后端运行,不阻塞前端

  docker-compose restart  重启所有容器。

 

 自此所有容器成功运行,但我感觉还不够,因为一直都是在windos上玩。而没有上CentOS7,可我又不缺CentOS环境。所以要玩一把

net core Api 跨平台部署

技术栈:Jexus,mysql,mssql,redis

关于jexus部署net core 可以参考我前面写的文章:https://www.cnblogs.com/nsky/p/10386460.html

既然要加入新的成员。jexus 和 mssql,那么就得修改docker-compose文件

 在通过docker-compose统一打包前,我们先来单独玩玩mssql

准备数据卷挂载目录

 data:保存数据库文件

sql:执行的脚本。mssql没有mysql的docker-entrypoint-initdb.d 挂载,启动mysql就执行sql。这里sql文件夹

虽然保存的是.sql文件。但要手动执行,不知道是不是我没有找到具体的方案

sql里面放一个init.sql文件。编写sql脚本如下

 这里要注意一点,一条语句完成必须要带一个Go语句

参考官方文档:

https://docs.microsoft.com/zh-cn/sql/linux/quickstart-install-connect-docker?view=sql-server-2017&pivots=cs1-bash

https://docs.microsoft.com/zh-cn/sql/linux/tutorial-restore-backup-in-sql-server-container?view=sql-server-2017

镜像文档:https://hub.docker.com/_/microsoft-mssql-server

复制代码
//注释部分
docker run -d -p 1433:1433 \
-e ACCEPT_EULA=Y \ #确认您接受最终用户许可协议。
-e SA_PASSWORD=DockerPwd123 \ #强大的系统管理员(SA)密码:至少8个字符,包括大写,小写字母,基数为10的数字和/或非字母数字符号。
-e MSSQL_PID=Express \ #版本(Developer,Express,Enterprise,EnterpriseCore)默认值:Developer  
-v /docker/mssql:/var/opt/mssql \  # 映射数据库
v /d/docker/mssql/sql:/script #把需要执行的脚本放这里,script路径随便改,不是初始化执行,是手到执行
--name mssql #容器名称
mcr.microsoft.com/mssql/server #镜像
复制代码

执行成功后。数据卷挂载目录。生成了文件

此时data也有默认的数据库了

 通过 MSSMS(  Microsoft SQL Server Management Studio )连接试试

 刚上面说了sql中文件是没有被执行的。必须手动执行。

手动执行前,先来看看其他一些相关命令

进入容器后: docker exec -it mssql bash 

登陆数据库:localhost也可以用指定的ip代替,如果有端口。则带端口号即可

/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P DockerPwd123

-S 是服务器,不管端口是多少,都不用写
-U 是用户名 
-P 是密码

如果出现 1> 说明的登陆成功了

可以输入语句:select getdate() 试试,回车后,需要加Go语句,不过日期怎么不对?好像是相差8个时区

 执行sql中的文件

登陆容器后执行操作: /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P DockerPwd123 -i /script/init.sql 

 

挂载目录也有,这样就算容器无法进入。数据库也存在

由于时间问题,docker-compose 就不加入mssql,只加jexus,修改docker-compose如下

阿里云安装docker-compose 特别慢,今天就不写。后期在加上

关键时刻掉链子,写了这么多。其实也就一点皮毛而已,docker强大之处远远不止这些

未完待续

Portainer管理镜像

上传镜像到hub.docker

源码:https://github.com/byniqing/docker-compose

猜你喜欢

转载自www.cnblogs.com/jinanxiaolaohu/p/10856359.html