Docker從入門到動手實踐
一些理論知識,我這裡就不累贅了
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] 14public class UserInfoController : ControllerBase 15{ 16private DbUserInfoContext _DbUserInfoContext; 17public UserInfoController(DbUserInfoContext context) 18{ 19_DbUserInfoContext = context; 20} 21[HttpGet] 22public async Task<IActionResult> Get() 23{ 24return 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