Docker從入門到動手實踐

糯米粥發表於2019-05-12

一些理論知識,我這裡就不累贅了

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/out11 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

 

相關文章