顯示具有 docker 標籤的文章。 顯示所有文章
顯示具有 docker 標籤的文章。 顯示所有文章

2019年8月4日 星期日

在微軟官方Asp.net Core runtime image裡安裝powershell

在微軟官方Asp.net Core runtime image裡安裝powershell

微軟官方 asp.net core 的image 其實是base 在 nano server上(https://github.com/dotnet/dotnet-docker/blob/master/2.2/runtime/nanoserver-1809/amd64/Dockerfile)

而nano server 為了縮小體積早就己經移除powershell。但是powershell是個好東西,沒有powershell對我們要做一些客製的行為很麻煩,所以才會想怎麼才能加回來。

其實也沒什麼密訣,現在微軟都把官方image的DockerFile 都放在Github上了,找到官方powershell image 的 Github,看一下人家怎麼做照做就是了

https://github.com/PowerShell/PowerShell-Docker/blob/master/release/stable/nanoserver/docker/Dockerfile

原理就是先找一個有powershell的image ,下載powershell core (註)裝起來後再Copy到 nano server 上

以下是我的DockerFile

# escape=`

FROM mcr.microsoft.com/windows/servercore:ltsc2019 AS installer-env

ARG PS_VERSION=6.2.0
ARG PS_PACKAGE_URL=https://github.com/PowerShell/PowerShell/releases/download/v${PS_VERSION}/PowerShell-${PS_VERSION}-win-x64.zip

SHELL ["C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "-command"]

ARG PS_PACKAGE_URL_BASE64

RUN Write-host "Verifying valid Version..."; `
    if (!($env:PS_VERSION -match '^\d+\.\d+\.\d+(-\w+(\.\d+)?)?$' )) { `
        throw ('PS_Version ({0}) must match the regex "^\d+\.\d+\.\d+(-\w+(\.\d+)?)?$"' -f $env:PS_VERSION) `
    } `
    $ProgressPreference = 'SilentlyContinue'; `
    if($env:PS_PACKAGE_URL_BASE64){ `
        Write-host "decoding: $env:PS_PACKAGE_URL_BASE64" ;`
        $url = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($env:PS_PACKAGE_URL_BASE64)) `
    } else { `
        Write-host "using url: $env:PS_PACKAGE_URL" ;`
        $url = $env:PS_PACKAGE_URL `
    } `
    Write-host "downloading: $url"; `
    [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12; `
    Invoke-WebRequest -Uri $url -outfile /powershell.zip -verbose; `
    Expand-Archive powershell.zip -DestinationPath  \PowerShell

# 裝到asp.net core 2.2 runtime 的 image上
FROM mcr.microsoft.com/dotnet/core/aspnet:2.2     

# Copy PowerShell Core from the installer container
ENV ProgramFiles="C:\Program Files" `
    # set a fixed location for the Module analysis cache
    LOCALAPPDATA="C:\Users\ContainerAdministrator\AppData\Local" `
    PSModuleAnalysisCachePath="$LOCALAPPDATA\Microsoft\Windows\PowerShell\docker\ModuleAnalysisCache" `
    # Persist %PSCORE% ENV variable for user convenience
    PSCORE="$ProgramFiles\PowerShell\pwsh.exe"

# Copy PowerShell Core from the installer container
COPY --from=installer-env ["\\PowerShell\\", "$ProgramFiles\\PowerShell\\latest"]

# Set the path 這裡要注意一下,setx /M 需要管理者的權限,
# 但原本的身分是ContainerUser,所以需要先切換身分到ContainerAdministrator ,設完再切回來

USER ContainerAdministrator
RUN setx /M PATH "%ProgramFiles%\PowerShell\latest;%PATH%;"
USER ContainerUser

# intialize powershell module cache
RUN pwsh `
        -NoLogo `
        -NoProfile `
        -Command " `
          $stopTime = (get-date).AddMinutes(15); `
          $ErrorActionPreference = 'Stop' ; `
          $ProgressPreference = 'SilentlyContinue' ; `
          while(!(Test-Path -Path $env:PSModuleAnalysisCachePath)) {  `
            Write-Host "'Waiting for $env:PSModuleAnalysisCachePath'" ; `
            if((get-date) -gt $stopTime) { throw 'timout expired'} `
            Start-Sleep -Seconds 6 ; `
          }"

P.S. 新版的powershell ,己經可以跨平台了,並且改名叫 powershell core 了,而且執行檔名稱也改叫 pwsh.exe,不再是 powershell.exe

2019年7月25日 星期四

在winodws container 使用 chrome headless 做 angular unit test

在winodws container 使用 chrome headless 做 angular unit test

現在 azure pipeline 來到前端的unit test 階段
想像中應該就像在自己電腦裡執行一樣,如果可以正常用chrome headless 執行 unit test 放到docker 應該不會有問題。BUT 事情總是沒有這麼單純。

以下是我部份的karma.config.js 跟DockerFile

module.exports = function (config) {
  config.set({
    customLaunchers: {
      ChromeHeadlessDocker: {
        base: 'ChromeHeadless',
        flags: [
          '--disable-web-security',
          '--no-sandbox',
          '--disable-setuid-sandbox',
          '--disable-dev-shm-usage',
          '--remote-debugging-port=9223',
          '--headless',
          '--disable-gpu'
        ]
      }
    },
    .
    .
    .
  });
};

DockerFile

RUN ng test --watch=false --code-coverage --browsers=ChromeHeadlessDocker --reporters=junit,progress

結果一直會出現

ChromeHeadless have not captured in 60000 ms, killing.

這才想到…我沒有裝chrome在docker container裡面

原本我是用

RUN Invoke-WebRequest -OutFile ChromeStandaloneSetup64.exe https://dl.google.com/tag/s/installdataindex/update2/installers/ChromeStandaloneSetup64.exe;
RUN ./ChromeStandaloneSetup64.exe /silent /install

但是卻一直裝不成功。( 如果有裝成功會在 C:\Program Files (x86)\ 底下有個Google 的目錄)
後來到 https://cloud.google.com/chrome-enterprise/browser/download/ 下載MSI的版本,先COPY到 image裡面再下指令裝

COPY ./GoogleChromeStandaloneEnterprise64.msi /
RUN Start-Process -FilePath msiexec -ArgumentList /q, /i, GoogleChromeStandaloneEnterprise64.msi -Wait ; `
Remove-Item -Force GoogleChromeStandaloneEnterprise64.msi

確認chrome有裝起來之後,再執行docker build 還是出現

ChromeHeadless have not captured in 60000 ms, killing.

把 karma.config.js 裡的 logLevel: config.LOG_INFO, 改成 config.LOG_DEBUG 也沒得到什麼有用的資訊

這個時候己經快瘋了,在這裡卡關卡了三天,無論改config或者一些奇奇怪怪的方法都沒效。

而且重點是網路上找到的方法,其實都是Linux container的解決方案。 Windows Container 根本一整個弱掉了。

後來終於在這裡找到 https://stackoverflow.com/questions/52948655/chrome-on-windows-docker-container-is-not-working 原來是缺少字型的問題. 一整個 WHAT THE ~~~

這位好心的 Prom3theu5 還提供了字型及安裝方法
https://github.com/prom3theu5/ServerCoreFonts

照著上面的方法,安裝字型之後終於可以看到開始測試的訊息了。

2019-07-08T00:39:39.6996247Z  [32m08 07 2019 08:39:39.675:INFO [karma-server]:  [39mKarma v3.1.1 server started at http://0.0.0.0:9876/

2019-07-08T00:39:39.6999650Z  [32m08 07 2019 08:39:39.698:INFO [launcher]:  [39mLaunching browsers ChromeHeadlessCI with concurrency unlimited

2019-07-08T00:39:39.7124483Z  [32m08 07 2019 08:39:39.710:INFO [launcher]:  [39mStarting browser ChromeHeadless

2019-07-08T00:40:35.5387718Z  [32m08 07 2019 08:40:35.537:INFO [HeadlessChrome 0.0.0 (Windows 10 0.0.0)]:  [39mConnected on socket Gsf6g9YFWi0lxoFVAAAA with id 57991930

2019-07-08T00:40:49.6356010Z HeadlessChrome 0.0.0 (Windows 10 0.0.0): Executed 0 of 439 SUCCESS (0 secs / 0 secs)

2019-07-08T00:40:49.7481854Z  [1A [2KHeadlessChrome 0.0.0 (Windows 10 0.0.0): Executed 1 of 439 SUCCESS (0 secs / 0.079 secs)

2019年7月10日 星期三

在Azure Pipeline 裡使用 docker build 執行 asp.net core 的 unit test 後,把測試結果跟code coverage 上傳到組建裡

在Azure Pipeline 裡使用 docker build 執行 asp.net core 的 unit test 後,把測試結果跟code coverage 上傳到組建裡

在Pipeline建置時,如果要看到測試結果跟code coverage , 需要在Pipeline中建置,才能在azure devops server 的介面中看到測試結果跟程式碼含蓋範圍。像這樣
enter image description here

但是如果要把unit test 放在 docker 裡,在 multi stage builds 的過程之中執行unit test,在預設的狀況下,測試結果會在某一個build stage中,就不能使用pipeline的task來上傳測試結果。

解決方法

在docker 建置過程中做unit test , 另外產生測試結果。等到docker build 結束之後,用powershell把測試結果copy出來,然後再分別用 pipeline 的task 上傳測試結果及程式碼含蓋範圍。 就像下圖的步驟 3,4,5一樣。

enter image description here

Build docker image

因為我有好幾個測試專案,預設會產生好幾個code coverage的report 。 所以在執行dotnet test 有一些特殊的參數來讓這些report 合併成一個。

第一個是 CoverletOutputFormat=“json%2ccobertura” ,用來指定產出report的格式,注意這個 %2c 是分號 ; 的意思。 但是如果直接寫;在docker build 時會有powershell的錯誤。弄了很久才找到解法,改成%2C (是ascii code ; 的意思)就可以過了。

第二個是MergeWith=/testresults/coverage/coverage.json 。用MergeWith 來指定多個 code coverage 的report 跟誰合併,要指定report的位置在個目錄。

另外,在dockerfile 裡我有下了一個 label 叫 test=true ,這個是用來讓docker build 完之後還可以找到暫存的stage ,從裡面把我們的測試報告copy出來。

底下是我部份的dockerfile , 我的web.sln 裡面有3個程式專案及4個測試專案。

RUN dotnet restore web.sln
RUN dotnet build web.csproj -c Release -o /app/out 

# unit test
#先下一個label 
LABEL test=true
# 安裝dotnet-reportgenerator-globaltool 用來產生cobertura 格式的 code coverage report
RUN dotnet tool install dotnet-reportgenerator-globaltool --version 4.2.2 --tool-path /tools  

# %2c 是 ;(分號) 很重要!! 很重要!! 因為直接用; 會出錯。
RUN dotnet test --results-directory /testresults --logger:trx /p:CollectCoverage=true /p:CoverletOutputFormat="json%2ccobertura" /p:MergeWith=/testresults/coverage/coverage.json /p:Exclude="[xunit.*]*" /p:CoverletOutput=/testresults/coverage/ web.sln

RUN /tools/reportgenerator.exe "-reports:/testresults/coverage/coverage.cobertura.xml"  "-targetdir:/testresults/coverage/reports"  "-reporttypes:HTMLInline`;HTMLChart"

用powershell 取得測試結果

enter image description here

script說明

  1. 先找到下label=test的 docker id, 預設會新的在最上面,所以抓第 0 個
  2. 用找到的id 建立一個臨時的container
  3. 從container把測試結果copy 出來
  4. 這個臨時的container就可以移除惹
$id=$($(docker images --filter "label=test=true" -q)[0]);
docker create --name testcontainer $id;
docker cp testcontainer:/testresults ./;
docker rm testcontainer;

發行測試結果

enter image description here
測試結果格式選 VSTest
搜尋資料夾要選對,我的例子是 testresults

發行程式碼涵蓋範圍結果

enter image description here
程式碼涵蓋範圍工具 選cobertura
摘要檔案 跟 報告目錄 路徑要設對


如果都成功了,就可以在組建的摘要裡看到測試結果了,就像第一張圖一樣。 也可以在測試的tab看到詳細的結果
enter image description here

同場加映

看完上面的,有沒有感到奇怪,為什麼沒有code coverage 的詳細資料。
因為這是 Azure Devops Server 2019 目前版本的bug,但是線上版的Azure devops 是有詳細資料的。

在 2019/5/22 己經有人回報給 visual studio team了 ,MS也有回覆下個release會修好。

Code coverage tab missing in Azure DevOps Server

另外

前端(angular) 的unit test 也可以用類似的方法,產出測試報告跟上傳到devops server上,有空再來分享另一段血淚史。

2019年6月14日 星期五

docker compose 在重開機之後無法啟動

docker compose 在重開機之後無法啟動.md

今天在試時 docker compose 發現host 重開機之後,下指令啟動,但卻無法啟動。

C:\web>docker-compose.exe start
Starting proxy      ... error
Starting site1      ... error
Starting site2      ... error

ERROR: for proxy  Cannot start service proxy: network 02fda54dd66e8782205ee417e901b198279abd40f74b2ffd970fcabc336db8eb not found
ERROR: No containers to start

看起來是找不到network。 真是見鬼了,用network ls 一看,真的不見了。

C:\web>docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
202191da323c        nat                 nat                 local
6f2b7ad221fe        none                null                local

後來才想起來 windows server 2019 重開機之後不會保留 driver 為 nat 的 network。
另一個悲傷的故事

既然 start 的指令無法啟動,那 up 指令會怎樣呢?

C:\web>docker-compose.exe up -d
Creating network "web_lan" with driver "nat"
Starting proxy ... error

ERROR: for proxy  Cannot start service proxy: network 02fda54dd66e8782205ee417e901b198279abd40f74b2ffd970fcabc336db8eb not found

ERROR: Encountered errors while bringing up the project.

看到第一行輸出時,心裡還 “耶” 了一下,結果第二行就…

這個也很奇怪,明明network建起來了,但是找不到??? 一整個黑人問號
用network ls看,還真的有建起來

C:\web>docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
88d33743ebc0        web_lan             nat                 local
202191da323c        nat                 nat                 local
6f2b7ad221fe        none                null                local

仔細一看network id ,原來不一樣了。 哇哩!! 新建的network id不一樣, 但是docker不會自動使用新的但是同名的network!!!

那…全部刪掉再建總可以了吧!!

docker-compose down
docker-compose up -d

總算建起來了。
但是只能這樣解嗎? 底下是我的 docker-compose.yml

version: '3.7'

services:
 proxy:
  image : proxy
  container_name: proxy
  restart: always
  networks:
   lan: 
  ports:
   - "80:80"

 site1:
  image : web
  container_name: site1
  ports:
   - "8000:80"
  networks:
   lan:
  depends_on:
   - "proxy"
  restart: always
 site2:
  image : web
  container_name: site2
  ports:
   - "8001:80"
  networks:
   lan:
  depends_on:
   - "proxy"
  restart: always 
networks:
 lan:
  driver: nat

lan 這個network是跟著docker compose一起建起來的。 那不要一起建,先在外面建好總可以吧!!
底下是修改過的 docker-compose.yml

version: '3.7'

services:
 proxy:
  image : proxy
  container_name: proxy
  restart: always
  networks:
   lan: 
  ports:
   - "80:80"

 site1:
  image : web
  container_name: site1
  ports:
   - "8000:80"
  networks:
   lan:
  depends_on:
   - "proxy"
  restart: always
 site2:
  image : web
  container_name: site2
  ports:
   - "8001:80"
  networks:
   lan:
  depends_on:
   - "proxy"
  restart: always 
networks: 
 lan: 
  external: 
   name: nat

建立一個network 叫lan,連結到外部現有的network叫 “nat” 。

#新建時就不會建立新的network
C:\web>docker-compose up -d
Creating proxy      ... done
Creating site1      ... done
Creating site2      ... done

#移除時,docker會發現是外部的network然後就略過
C:\web>docker-compose down
Removing site2      ... done
Removing site1      ... done
Removing proxy      ... done
Network nat is external, skipping

在windows server 2019上使用 docker 建立 driver 為 nat 的 network 重開機之後會消失

在windows server 2019上使用 docker 建立 driver 為 nat 的 network 重開機之後會消失

一開始我以為我發現了一個 windows server 2019 (1809) 的BUG 。

docker create network -d nat mynet

用上面的指令 在docker create network driver 用 nat 的話 ,重開機後,mynet就不見惹。

2018/12 月在 github 就有人 report 這個issue , 但是一直沒修。 https://github.com/docker/for-win/issues/3076

再深入追查才發現這是微軟預設的行為… 我的老天鵝啊!!! 這不合理吧!!!

NAT networks created on Windows Server 2019 (or above) are no longer persisted after reboot.

https://docs.microsoft.com/en-us/virtualization/windowscontainers/container-networking/network-drivers-topologies

並沒有解釋為什麼~~~ 啊啊啊!!! (請允許我內心的哀嚎) 這樣的預設設定不是自癈武功嗎??

山不轉路轉

每次重開機重建network 總行了吧!!

用下面的powershell script 用工作排程器在開機時執行

$networkName = "mylan"
$ErrorActionPreference = "silentlycontinue"
$inspectResult = & docker network inspect $networkName
$ErrorActionPreference = "continue"
#如果network 不存在就建一個新的
if($LASTEXITCODE -eq 1) {
  docker network create -d nat --subnet 192.168.1.0/24 -o com.docker.network.windowsshim.networkname=Docker-$networkName $networkName
} 

哀嚎加演場

後來測試,建立新的network 然後 driver 用 transparent

docker create network -d transparent mynet

一樣會不見!! 我本來己經準備在臉上寫一個慘字了。

還好做完 windows update , network driver 是 transparent 就不會在重開機之後不見了。 應該是 KB4497934KB4494441 其中之一修好的。
windows update for server 2019

但是我在KB的更新說明裡面找不到相關的說明,算了,反正有修就好。

docker for windows 的 volume 無法掛載網路磁碟機

docker for windows 的 volume 無法掛載網路磁碟機

在windows container怎樣把網路磁碟機掛成 Volume, 試了很久。 包括 net use 成一個碟碟機之後再試了就是掛不進去。
google 很久找到的 solution 都是 for linux 的. 在 windows 上要把網路上的磁碟機掛成 volume 似乎就只有類似像 iSCSI 這種方式而己。

山不轉路轉

後來突然轉念, 直接在 container 裡掛起來就好了啊

在container裡下

net use z: "\\172.16.0.10\folder" /user:eric mypassword

還可以再把網路磁碟機變成C槽的某個資料夾

mklink /d z:\ c:\files

2019年5月16日 星期四

Docker container 重新啟動之後IP 會改變

Docker container 重新啟動之後IP 會改變

Docker container 重新啟動之後IP 會改變

如果container run在同一個network之下,而且要互通,像下面m1,m2這2個container這樣

docker run -d --net my-net --name m1 myimg
docker run -d --net my-net --name m2 myimg

一開始是可以從 m1 ping(連)的到m2

docker exec -i m1 cmd /c ping m2

Ping m2 [172.20.3.101] (使用 32 位元組的資料):
回覆自 172.20.3.101: 位元組=32 時間=1ms TTL=52

m2 重啟後

docker stop m2
docker start m2

就ping不到m2了

docker exec -i m1 cmd /c ping m2

Ping m2 [172.20.3.101] (使用 32 位元組的資料):
要求等候逾時。

經過數個小時的奮鬥(我的青春啊), 才發現原來每次container重啟IP就會變
而且在m1這台container裡,記的m2還是舊的ip,不會自動更新IP跟名稱對應。

所以,只要讓container的ip固定就好了

docker run -d --net my-net --ip 172.20.3.100 --name m1 myimg
docker run -d --net my-net --ip 172.20.3.101 --name m2 myimg

青春的血淚史…給跟我一樣是新手的你

2019年5月15日 星期三

讓Docker Container 裡的 IIS ,可以從 IIS管理員做遠端管理

讓Docker Container 裡的 IIS 可以從 IIS管理員做遠端管理
FROM microsoft/iis

#安裝 IIS 管理服務
RUN powershell Install-WindowsFeature Web-Mgmt-Service; \
New-ItemProperty -Path HKLM:\software\microsoft\WebManagement\Server -Name EnableRemoteManagement -Value 1 -Force; \
Set-Service -Name wmsvc -StartupType automatic;

#新增一個使用者,讓遠端管理可以使用這個帳號連線
RUN powershell net user 帳號 密碼 /ADD ;\
net localgroup administrators 帳號 /add ;

# IIS 管理服務使用的port是8172
EXPOSE 80/tcp 443/tcp 8172/tcp

假設上面build好的image叫iisremote

docker run -d -p 80:80 -p 8172:8172 iisremote

windows 10 的IIS 管理工具沒有可以連到遠端的功能

裝 IIS Manager for Remote Administration 1.2 就可以了
https://www.microsoft.com/en-us/download/details.aspx?id=41177