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日 星期四

在Azure Devops Server 2019 的 powershell task 執行外部執行檔

在Azure Devops Server 2019 的 powershell task 執行外部執行檔

原本的需求是在 Pipelines 部署時,判斷如果docker-compose 己經有把container 建起來了,就更新container,否則就用docker-compose建立新的container。

但是在Pipelines的部署工作裡,似乎要做到上面的需求有困難。所以才想要在powershell裡執行docker-compose

底下是我預備要執行的powershell,

Set-Location -Path C:\composefile

$proxy = 'iisproxy'
$site1 = 'site-blue'
$site2 = 'site-green'

$cID = $(docker ps -qf "name=$proxy")

if(-NOT $cID){
 Write-Host "create new container"
 docker pull myhub.local/iisproxy
 docker pull myhub.local/app
        docker-compose up -d 
    }
    else {
        Write-Host "update container"
 docker pull myhub.local/app
        docker-compose stop $site1 
        docker-compose up -d --force-recreate --no-deps $site1  
 Start-Sleep -s 5
        docker-compose stop $site2
        docker-compose up -d --force-recreate --no-deps $site2        
    }

但是卻發現只要是執行到 docker-compose 就會出現 NativeCommandError 的錯誤

2019-07-24T08:00:10.7991464Z ##[error]位於 C:\azagent\A1\_work\_temp\46980d80-fab6-4a21-8fd7-1a6e7130d9a8.ps1:20 字元:9

+ docker-compose stop $site1  

2019-07-24T08:00:10.8097416Z ##[error]+ ~~~~~~~~~~~~~~~~~~~~~~~~~~  

2019-07-24T08:00:10.8204529Z ##[error] + CategoryInfo : NotSpecified: (Stopping site-blue ... :String) [], RemoteException  

2019-07-24T08:00:10.8301628Z ##[error] + FullyQualifiedErrorId : NativeCommandError  

2019-07-24T08:00:10.8403452Z ##[error]  

2019-07-24T08:00:10.8516019Z ##[error]PowerShell 已結束,代碼為 '1'。

但是直接開啟command視窗執行是正常的。 後來又發現在Powershell ISE 裡執行指令就會有一樣的錯誤。 原來兇手是 powershell 自己,害我錯怪 docker-compose 好幾天。

自此,才有比較明確的追查方向。
找到在stackoverflow上一樣症頭的討論

https://stackoverflow.com/questions/2095088/error-when-calling-3rd-party-executable-from-powershell-when-using-an-ide/20950421#20950421

原來是因為 powershell 在處理 STDERR (standard error) 有不同的行為才造成這樣的錯

照文章內的好幾個做法,要在 Pipelines裡可以正常執行要改成這樣
用 2>&1 來吃掉多出來的錯誤

&cmd /c "docker-compose up -d 2>&1"

下面是完整的script

Set-Location -Path C:\composefile

$proxy = 'iisproxy'
$site1 = 'site-blue'
$site2 = 'site-green'

$cID = $(docker ps -qf "name=$proxy")

if(-NOT $cID){
 Write-Host "create new container"
 docker pull myhub.local/iisproxy
 docker pull myhub.local/app
        &cmd /c "docker-compose up -d 2>&1"
    }
    else {
        Write-Host "update container"
 docker pull myhub.local/app
        &cmd /c "docker-compose stop $site1 2>&1"
        &cmd /c "docker-compose up -d --force-recreate --no-deps $site1 2>&1"
 Start-Sleep -s 5
        &cmd /c "docker-compose stop $site2 2>&1"
        &cmd /c "docker-compose up -d --force-recreate --no-deps $site2 2>&1"
    }

在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月20日 星期四

Azure Devops Server 2019 更改電腦名稱

Azure Devops Server 2019 更改電腦名稱

在新VM裡安裝 Azure Devops Server 2019 時,沒有注意電腦名稱是什麼,後來想要改電腦名稱時,也沒想那麼多就直接改了。結果,改完重開機就連不上web 介面了,一直轉圈圈。
心裡想一定有什麼跟電腦名稱綁在一起無法執行。 後來在

C:\Program Files\Azure DevOps Server 2019\Application Tier\Web Services\web.config

找到資料庫連線字串的設定是用電腦名稱

    <add key="applicationDatabase" value="Data Source=WIN-IL123HEV1A2;Initial Catalog=AzureDevOps_Configuration;Integrated Security=True;Encrypt=False" />

把 Data Source 換成新的電腦名稱再重新開機一次就都正常了。

2019年6月18日 星期二

Azure Pipelines 建立部署群組卡關

Azure Pipelines 建立部署群組卡關

現在在測試新的Devpos環境, 以現有使用TFS的經驗,Azure Devops Server 2019 自然是第一個考慮的工具。

把Azure Devops Server 2019 的安裝比較沒什麼問題,基本上安裝程式會檢查環境,有缺什麼會顯示出來。例如:要先安裝SQL SERVER;要有JDK(沒有的話,打個勾勾會自動裝OpenJDK) 。 這個比遙遠的當年 TFS 2005 裝了好幾天才裝起來好太多了 。

不過安裝好之後,要在pipelines建第一個部署群組就卡關了,"部署群組"就是要部署到哪裡去,被部署的SERVER上面要裝一個代理程式( agent) 。

But… 安裝agent,居然是叫你自己下powershell command的方式。虧我前面還在誇Azure Devops Server 2019的安裝介面做的不錯。

雖然在介面上他己經產生好script,把他貼到powershell裡面去執行就好了。看起來一塊蛋榚啊!! 介面長的像這樣
部署群組圖1

照畫面上的指示,把那一堆Script放到powershell的command裡執行
結果
部署群組圖二

哇哩,反覆的仔細檢查script.

才發現179字元 “Administrator” 有問題, 把 “ ” 換成正常的 "

enter image description here

再來一次

PS C:\> $ErrorActionPreference="Stop";If(-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent() ).IsInRole( [Security.Principal.WindowsBuiltInRole] "Administrator")){ throw "在系統管理員 PowerShell 提示字元處執行命令"};If($PSVersionTable.PSVersion -lt (New-Object System.Version("3.0"))){ throw "指令碼 (3.0) 需要的 Windows PowerShell 最低版本,與目前正在執行的 Windows PowerShell 版本不符。" };If(-NOT (Test-Path $env:SystemDrive\'azagent')){mkdir $env:SystemDrive\'azagent'}; cd $env:SystemDrive\'azagent'; for($i=1; $i -lt 100; $i++){$destFolder="A"+$i.ToString();if(-NOT (Test-Path ($destFolder))){mkdir $destFolder;cd $destFolder;break;}}; $agentZip="$PWD\agent.zip";$DefaultProxy=[System.Net.WebRequest]::DefaultWebProxy;$securityProtocol=@();$securityProtocol+=[Net.ServicePointManager]::SecurityProtocol;$securityProtocol+=[Net.SecurityProtocolType]::Tls12;[Net.ServicePointManager]::SecurityProtocol=$securityProtocol;$WebClient=New-Object Net.WebClient; $Uri='https://go.microsoft.com/fwlink/?linkid=2066756';if($DefaultProxy -and (-not $DefaultProxy.IsBypassed($Uri))){$WebClient.Proxy= New-Object Net.WebProxy($DefaultProxy.GetProxy($Uri).OriginalString, $True);}; $WebClient.DownloadFile($Uri, $agentZip);Add-Type -AssemblyName System.IO.Compression.FileSystem;[System.IO.Compression.ZipFile]::ExtractToDirectory( $agentZip, "$PWD");.\config.cmd --deploymentgroup --deploymentgroupname "first" --agent $env:COMPUTERNAME --runasservice --work '_work' --url 'http://172.16.1.111/' --collectionname 'DefaultCollection' --projectname 'uuuu' --auth Integrated; Remove-Item $agentZip;


    目錄: C:\azagent


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        2019/6/17  下午 03:18                A1


>> 連線:

正在連線到伺服器...

>> 註冊代理程式:

正在掃描工具功能。
正在連接至伺服器。
請輸入 代理程式的部署群組標記? (Y/N) (請為 否 按 Enter) >
已成功新增代理程式
正在測試代理程式連線。
2019-06-17 07:22:07Z: 已儲存設定。
請輸入 要用於服務的使用者帳戶 (請為 NT AUTHORITY\SYSTEM 按 Enter) >
正在將檔案權限授與 'NT AUTHORITY\SYSTEM'。
已成功安裝服務 vstsagent.172.TEST
服務 vstsagent.172.TEST 已成功設定復原選項
服務 vstsagent.172.TEST 已成功設定為延遲自動啟動
已成功設定服務 vstsagent.172.TEST
已成功啟動服務 vstsagent.172.TEST

終於可以註冊上去了,握拳!!


同場加映:如何移除 agent

從上面的script可以發現,他其實只做2件事,

  1. 下載1個zip 檔,然後解壓縮到C:\azagent\A1
  2. 執行裡面的config.cmd 跟著一堆參數

所以要移除agent , 一樣用有管理員身分的powershell到C:\azagent\A1 裡執行 config.cmd 只是參數換成remove

PS C:\azagent\a1> .\config.cmd remove
正在移除服務
正在等候服務結束...
成功:正在移除服務
正在從伺服器移除代理程式
請輸入 驗證類型 (請為 Integrated 按 Enter) >
正在連線到伺服器...
成功:正在從伺服器移除代理程式
正在移除 .credentials
成功:正在移除 .credentials
正在移除 .agent
成功:正在移除 .agent

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年6月10日 星期一

WordPress的效能改善

WordPress的效能改善

先說結論

  1. 把PHP的版本升到最新版

  2. 修改 wp-config.php
    把原本

/** MySQL hostname */  
define( 'DB_HOST', 'localhost' );

改成

/** MySQL hostname */  
define( 'DB_HOST', '127.0.0.1' );
  1. 使用page cache 的 plugin 。(我是用 Cache Enabler ,應該是還有其他的選擇)

故事是這樣的

最近要把官網改版,然後使用wordpress來製作,這樣可以讓不懂程式的相關人員可以自己更動網站內容。

BUT ,代誌不是憨人想的那麼簡單

網站快上線時才發現,天啊!! 怎麼這麼慢。 Waiting TTFB (Time To First Byte) 居然要超過4秒。整個網頁跑完十多秒過去了。

於是開始神農嘗百草的日子,先是找到把PHP 從 5.6 升到 7.3 可以縮短約 2000 ms 。 把我們使用的 wordpress 樣版升到最新版本,也有快一點點縮短約500ms 。

現在 TTFB 來到約 2500 ms,還是太慢了。 再來試過很多方法,例如,開啟PHP的opcache , 加大PHP的可用記憶體(memory_limit) 都沒什麼作用。

弄很久才終於找到,把wp-config.php 裡mysql的位址從 localhost 改成 127.0.0.1 馬上縮短1000個ms。 為什麼有效的原因是,因為改成用 IP 之後 ,不需要作內部的 DNS 解析。

再加上wordpress 的 cache plugin,我是用 Cache Enabler 可以讓同一個page 的第二次 request 縮短約 1000ms。

現在首頁在有cache 的狀況下,TTFB大約在20-70ms。算還可以,網站就先這樣上線了。

2019年6月4日 星期二

在IIS 上 CSS,圖片,JS 這幾種類型的檔案, 突然出現 500 INTERNAL SERVER ERROR

在IIS 上 CSS,圖片,JS 這幾種類型的檔案, 突然出現 500 INTERNAL SERVER ERROR

今天在主要對外的網站收到想要讓使用者下載 apk 檔的需求,我想說一塊蛋榚,找一下apk 的MIME Type

application/vnd.android.package-archive

就直接把這個設定加到IIS上電腦層級的MIME Type 設定,然後就去忙別的事了。

沒多久就有人開始反應,全部網站的STYLE全跑掉了。 用開發者工具一看,天啊!! 所有的CSS , 圖片,跟javascript 檔全都回應狀態500。

一開始重啟IIS…沒效,重開機…沒效。 後來突然想到是不是我動了設定的關係,就把剛剛加的MIMETYPE 刪掉… 就好了!!

問一下Google 大神原來還不少人有遇過一樣的症頭。
解法就是改成在 web.config設定, 加入MIMETYPE前,先移掉該type。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <staticContent>
            <remove fileExtension=".apk" />
            <mimeMap fileExtension=".apk" mimeType="application/vnd.android.package-archive" />
        </staticContent>
    </system.webServer>
</configuration>

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日 星期三

設定 Nexus Repository OSS 3.X for Windows 啟用HTTPS(使用自我簽署憑證)

設定 Nexus Repository OSS 3.X for Windows 啟用HTTPS(使用自我簽署憑證)

需求

原始的需求只是要在內部環境中架設 Nexus Repository 作 docker private repository 但是 預設要使用IP連線還要加上 port 真的是又臭又長。

所以才會想要設定 Nexus Repository 讓他可以使用 https ,減少 docker image 在tag時太長,字要打很多很累的困擾。

例如,原本的指令

docker tag myimg 172.16.3.100:8000/myimg 

設定完之後只要

docker tag myimg docker.local/myimg 

可以少打很多字, 嗯…果然科技來自於惰性。

想法

利用電腦名稱取代ip (這裡用docker.local 為例; 把要當Repository的電腦取名叫 docker, 就可以用 docker.local 連的到)
而且因為docker client 預設是使用https 也就是443port 做為連線. 如果要儘量縮短網址讓 private repository 使用https連線是最好的做法.

其實也不一定要走https 就能達成目地, 只要在 docker client 修改 daemon.json,加上

"insecure-registries" : ["docker.local"] 

然後讓內部用的repository 使用 80port的http 就可以了 。
但缺點就是,每個docker client都要去修改 daemon.json。

所以為了避免去注意到底daemon.json到了改了沒(說到底還是懶) ,使用https還是比較好的做法。

環境

https://www.sonatype.com/download-oss-sonatype 下載最新版本 Nexus Repository OSS , 目前我是使用 3.16 的版本

步驟

以下步驟主要是參考 Nexus官方文件 https://help.sonatype.com/repomanager3/security/configuring-ssl#ConfiguringSSL-InboundSSL-ConfiguringtoServeContentviaHTTPS

1.用 OpenSSL tool產生給要private repository使用的domain (以 docker.local 為例)憑証 ,產生出來的格式要pfx , 而且匯出時一定要有6個字以上的密碼 ,這裡假設 檔名叫docker.local.pfx, 密碼是123456

怎麼用OpenSSL產生憑証可以參考黑暗大的這篇 https://blog.darkthread.net/blog/iis-ssl-cert-by-openssl/

還有記得要把簽發SSL憑証的根憑証匯到 docker client [本機電腦]層級的[受信任的根憑証單位]裡 , 如果有AD的環境可以用群組原則物件(GPO)派發到每一台電腦參考
https://docs.microsoft.com/zh-tw/windows-server/identity/ad-fs/deployment/distribute-certificates-to-client-computers-by-using-group-policy

2.用 java sdk 附的keytool 將步驟 1 產出的憑証轉成 jks 格式,檔名叫keystore.jks

範例

 "C:\Program Files\Java\jdk1.8.0_161\bin\keytool" -importkeystore -srckeystore docker.local.pfx -srcstoretype PKCS12 -srcstorepass 123456 -destkeystore keystore.jks -deststoretype jks -deststorepass 123456

3.把keystore.jks放到 Nexus 的安裝目錄下的/etc/ssl/ 裡

4.修改安裝目錄下的/etc/nexus-default.properties

#讓管理介面可以使用https port用 8443
application-port-ssl=8443

#把nexus-args= 那一行 變成 
nexus-args=${jetty.etc}/jetty.xml,${jetty.etc}/jetty-http.xml,${jetty.etc}/jetty-https.xml,${jetty.etc}/jetty-requestlog.xml

5.修改安裝目錄下的/etc/jetty/jetty-https.xml

把下列這3個值都改成憑証的密碼,這邊範例是123456

<Set name="KeyStorePassword">123456</Set>  
<Set name="KeyManagerPassword">123456</Set>  
<Set name="TrustStorePassword">123456</Set>  

6.重啟nexus這個windows 服務(如果有把nexus變成windows 服務)

7.用https://docker.local:8443 如果有連上管理介面就表示有啟用成功

8.然後就可以到管理介面把內部用的repository , 使用https打勾 ,內容填上443

9.用browser連 https://docker.local 有回應就表示設定成功

10.到docker client 使用login的指令測試是否有成功

docker login docker.local

如果出現詢問帳密 就表示設定成功

如果出現

Get https://docker.local/v2/: x509: certificate signed by unknown authority 

表示沒有把docker.local.pfx 的根憑証, 匯入到[本機電腦]層級的[受信任的根憑証單位]裡

讓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

使用Powershell在IIS上安裝SSL憑証

使用Powershell在IIS上安裝SSL憑証

使用Powershell在IIS上安裝SSL憑証

$securePfxPass = "你的密碼" | ConvertTo-SecureString -AsPlainText -Force
Import-PfxCertificate -Password $securePfxPass -CertStoreLocation Cert:\LocalMachine\My -FilePath c:\test.local.pfx   

#取得憑証指紋
$pfxThumbprint = (Get-PfxData -FilePath \test.local.pfx -Password $securePfxPass).EndEntityCertificates.Thumbprint

#新增 https 的繫結在 Default Web Site上
$binding = New-WebBinding -Name "Default Web Site" -Protocol https -IPAddress * -Port 443;
$binding = Get-WebBinding -Name "Default Web Site" -Protocol https;
# 綁定憑証 , 注意這裡的 "my" 是指憑証存放的Sotre , 不要亂改 , 指的是 Cert:\LocalMachine\My <-這個my
$binding.AddSslCertificate($pfxThumbprint, "my");