一些理論知識,我這裡就不累贅了
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://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