<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Docker 彙整 - 泰克哪裡去</title>
	<atom:link href="https://tech.uccu.website/category/it/docker/feed" rel="self" type="application/rss+xml" />
	<link>https://tech.uccu.website/category/it/docker</link>
	<description>一個科技相關的隨手記錄網站</description>
	<lastBuildDate>Sun, 09 Oct 2022 14:59:11 +0000</lastBuildDate>
	<language>zh-TW</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.1</generator>
<site xmlns="com-wordpress:feed-additions:1">119574712</site>	<item>
		<title>【2022鐵人賽】使用Task與CLI的抉擇</title>
		<link>https://tech.uccu.website/2022ironman-day22-task-or-cli.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=2022ironman-day22-task-or-cli</link>
					<comments>https://tech.uccu.website/2022ironman-day22-task-or-cli.html#respond</comments>
		
		<dc:creator><![CDATA[鳴人]]></dc:creator>
		<pubDate>Sat, 08 Oct 2022 14:37:38 +0000</pubDate>
				<category><![CDATA[2022鐵人賽]]></category>
		<category><![CDATA[Azure DevOps]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[iThome鐵人賽]]></category>
		<category><![CDATA[2022ironman]]></category>
		<category><![CDATA[azure devops]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[linux]]></category>
		<guid isPermaLink="false">https://tech.uccu.website/?p=2055</guid>

					<description><![CDATA[<p>透過CLI就是組Scripts來達成目的，所以彈性相對高很多。除了可以Build Source Code之外，要Pack Nuget Package也是同樣透過Sdk CLI就可以達成。但是如果是用Task的話，可能就會需要分成不同的Task。</p>
<p>這篇文章 <a href="https://tech.uccu.website/2022ironman-day22-task-or-cli.html">【2022鐵人賽】使用Task與CLI的抉擇</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p>這篇要來聊聊Azure DevOps Pipeline的Task與CLI的抉擇，為什麼會這樣說呢？</p>



<p>因為其實在Azure DevOps Pipeline中已經有提供許多不錯的Task設計，尤其.Net的程式在Azure DevOps Pipeline中有各式各樣對應的Task可以使用，像去年的文章「<a href="https://tech.uccu.website/2021ironman-day7-first-pipeline-template-and-editor.html">CI/CD從這裡：設定第一個Pipeline(範本與編輯介面介紹)</a>」裡面就是使用內建的VSBuild Task來編譯.Net的程式碼，但是今年卻在「<a href="https://tech.uccu.website/2022ironman-day8-ci-pipeline-buildcode.html" target="_blank" rel="noreferrer noopener">基本版-建立CI Pipeline(1)</a>」文章內一開始就使用.Net Sdk的Container來編譯.Net的程式碼…</p>



<p>除了今年的文章屬於進階應用之外，還有其它幾個不同的因素考量：</p>



<ul class="wp-block-list"><li>Task要透過GUI才能快速方便知道有什麼參數可以選擇與設定</li><li>不同功能可能要選用不同task</li><li>可以達到相同功能的Task可能不只一個</li><li>想要測試看看執行結果如何無法在Pipeline以外的環境執行</li><li>用Container或直接用Sdk CLI可以在本機執行看看結果</li><li>新功能或指令在Task可能沒有設計或支援</li><li>如果是自管的Agent只要裝了Docker就可以</li></ul>



<p>因為透過CLI就是組Scripts來達成目的，所以彈性相對高很多。例如.Net Core Sdk CLI除了可以Build Source Code之外，要Pack Nuget Package也是同樣透過Sdk CLI就可以達成，只是指令的組成不太一樣。但是如果是用Task的話，就會需要分成Nuget Task與.Net Core Task，或是pack也用.Net Core Task而不是用Nuget Task。</p>



<p>另外還有像是我們使用Azure DevOps Artifacts功能來存放私有的Nuget Packages，不過要Push上去是需要身份驗證授權的，在Task的選擇部份就有Nuget authenticate Task(搭配設定Service Connection)，或是有個nuget.config檔案，裡面寫入PAT然後放在特定的位置來自動解決身份驗證的問題。</p>



<p>所以原本的dotnet-build-in-linux-container.yaml就可以重新做一份step template：</p>



<pre class="wp-block-prismatic-blocks"><code class="language-yaml"># steps/dotnet-sdk-in-linux-container.yaml

parameters:
  - name: dotnetSdkImgRepository
    type: string
    default: mcr.microsoft.com/dotnet/sdk:6.0-alpine
  - name: srcPath
    type: string
  - name: outputPath
    type: string
    default: $(Build.ArtifactStagingDirectory)/outputFiles/
  - name: slnOrCsprojName
    type: string
    default: Pipeline.sln
  - name: dotnetCommand
    type: string
  - name: dotnetCommandArgs
    type: string

steps:
  - script: |    
      echo &quot;Dotnet sdk in linux container template&quot;
      echo &quot;${{ convertToJson(parameters) }}&quot;
    displayName: Print template parameters
  - task: Bash@3
    displayName: Run dotnet sdk command in container
    inputs:
      targetType: &#039;inline&#039;
      script: |
        if [ -d ${{ parameters.outputPath }} ]; then echo &quot;${{ parameters.outputPath }} exist&quot;; else mkdir -p ${{ parameters.outputPath }}; fi
        export UID=$(id -u)
        export GID=$(id -g)
        docker run --user $UID:$GID --rm \
        -v ${{ parameters.srcPath }}:/tmp/source \
        -v ${{ parameters.outputPath }}:/tmp/publish \
        -e DOTNET_CLI_HOME=/tmp/.dotnet \
        ${{ parameters.dotnetSdkImgRepository }} \
        dotnet ${{ parameters.dotnetCommand }} /tmp/source/${{ parameters.slnOrCsprojName }} \
        -o /tmp/publish ${{ parameters.dotnetCommandArgs }} \
  - task: ArchiveFiles@2
    displayName: 壓縮成zip
    inputs:
      rootFolderOrFile: $(Build.BinariesDirectory)
      includeRootFolder: false
      archiveType: &#039;zip&#039;
      archiveFile: &#039;$(Build.ArtifactStagingDirectory)/zipFiles/buildResult.zip&#039;
      replaceExistingArchive: true</code></pre>



<p>這樣就可以用同樣的一個範本搞定Build Source Code、Pack Nuget Package之類的事情，對應在jobs/buildCode.yaml就是把引用的template檔案改成新的，然後加上dotnetCommand、dotnetCommandArgs參數的設定就行了。</p>



<pre class="wp-block-prismatic-blocks"><code class="language-yaml">      - template: ../steps/dotnet-sdk-in-linux-container.yaml
        parameters:
          srcPath: ${{ parameters.sourcePath }}
          slnOrCsprojName: ${{ parameters.slnOrCsprojName }}
          dotnetCommand: publish
          dotnetCommandArgs: &#039;-c ${{ parameters.buildConfiguration }}&#039;</code></pre>
<p>這篇文章 <a href="https://tech.uccu.website/2022ironman-day22-task-or-cli.html">【2022鐵人賽】使用Task與CLI的抉擇</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tech.uccu.website/2022ironman-day22-task-or-cli.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2055</post-id>	</item>
		<item>
		<title>【2022鐵人賽】基本版-建立CI Pipeline(3)</title>
		<link>https://tech.uccu.website/2022ironman-day10-ci-pipeline-deploy-dev-to-cloudrun.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=2022ironman-day10-ci-pipeline-deploy-dev-to-cloudrun</link>
					<comments>https://tech.uccu.website/2022ironman-day10-ci-pipeline-deploy-dev-to-cloudrun.html#respond</comments>
		
		<dc:creator><![CDATA[鳴人]]></dc:creator>
		<pubDate>Sun, 25 Sep 2022 13:59:09 +0000</pubDate>
				<category><![CDATA[2022鐵人賽]]></category>
		<category><![CDATA[Azure DevOps]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Google雲端GCP]]></category>
		<category><![CDATA[iThome鐵人賽]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[雲端]]></category>
		<category><![CDATA[2022ironman]]></category>
		<category><![CDATA[azure devops]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[gcp]]></category>
		<guid isPermaLink="false">https://tech.uccu.website/?p=1930</guid>

					<description><![CDATA[<p>這篇內容利用了Google建置的gcloud的Docker Image來執行gcloud CLI工具的指令，除了先前建立的服務帳戶要補上一個IAM的角色之外，透過Container執行gcloud指令還有一個重點，它將會是能不能夠正常透過Container執行gcloud的關鍵！</p>
<p>這篇文章 <a href="https://tech.uccu.website/2022ironman-day10-ci-pipeline-deploy-dev-to-cloudrun.html">【2022鐵人賽】基本版-建立CI Pipeline(3)</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></description>
										<content:encoded><![CDATA[<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" fetchpriority="high" decoding="async" width="990" height="150" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/00-DevelopCI-Flow.png?resize=990%2C150&#038;ssl=1" alt="" class="wp-image-1931" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/00-DevelopCI-Flow.png?w=990&amp;ssl=1 990w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/00-DevelopCI-Flow.png?resize=300%2C45&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/00-DevelopCI-Flow.png?resize=768%2C116&amp;ssl=1 768w" sizes="(max-width: 990px) 100vw, 990px" /><figcaption>Develop CI流程</figcaption></figure></div>


<p>上面這張圖是<a href="https://tech.uccu.website/2022ironman-day3-flow-plan.html" target="_blank" rel="noreferrer noopener">流程規劃說明</a>裡面畫的Develop CI Pipeline流程，我們已經在前兩篇完成了Build(Code)、Build Image，剩下最後一步就是Deploy Dev環境，這一篇就來完成這最後一步吧！</p>



<pre class="wp-block-prismatic-blocks"><code class="language-yaml">trigger:
- none

pool:
  vmImage: ubuntu-latest

resources:
  repositories:
  - repository: sources
    type: git
    name: ironman2022/NetApp
    ref: Develop
    trigger:
      branches:
        include:
          - Develop

variables:
  pipelineArtifact: output
  buildResultZipName: buildResult.zip
  slnOrCsprojName: IronmanWeb.sln
  imgRepository: &#039;asia-east1-docker.pkg.dev/feisty-mechanic-363012/ironman2022/ironmanweb&#039;
  buildDockerfile: &#039;Dockerfile&#039;
  imgRegistryService: &#039;GCPArtifactRegistry&#039;
  cloudRunServiceName: ironmanweb
  cloudRunPort: 8080
  cloudRunRegion: asia-east1
  cloudRunProjectId: feisty-mechanic-363012
  gcpAuthJsonFile: ironman2022-gcp-key.json

jobs:
  - job: BuildCode
    steps:
      - checkout: sources
        clean: true
      - script: |
          export UID=$(id -u)
          export GID=$(id -g)
          docker run --user $UID:$GID --rm \
          -v $(Build.SourcesDirectory):/tmp/source \
          -v $(Build.BinariesDirectory):/tmp/publish \
          -e DOTNET_CLI_HOME=/tmp/.dotnet \
          mcr.microsoft.com/dotnet/sdk:6.0-alpine \
          dotnet publish /tmp/source/$(slnOrCsprojName) \
          -c release \
          -o /tmp/publish
        displayName: Dotnet Build
      - task: ArchiveFiles@2
        displayName: 壓縮成zip
        inputs:
          rootFolderOrFile: $(Build.BinariesDirectory)
          includeRootFolder: false
          archiveType: &#039;zip&#039;
          archiveFile: &#039;$(Build.ArtifactStagingDirectory)/zipFiles/$(buildResultZipName)&#039;
          replaceExistingArchive: true
      - task: PublishBuildArtifacts@1
        displayName: 上傳到Pipeline Artifact
        inputs:
          PathtoPublish: &#039;$(Build.ArtifactStagingDirectory)/zipFiles/$(buildResultZipName)&#039;
          ArtifactName: &#039;$(pipelineArtifact)&#039;
          publishLocation: &#039;Container&#039;
  - job: BuildImage
    dependsOn: BuildCode
    steps:
      - task: DownloadBuildArtifacts@0
        displayName: 下載Pipeline Artifact
        inputs:
          buildType: &#039;current&#039;
          cleanDestinationFolder: true
          downloadType: &#039;single&#039;
          artifactName: &#039;$(pipelineArtifact)&#039;
          downloadPath: &#039;$(System.ArtifactsDirectory)/&#039;
      - task: ExtractFiles@1
        displayName: Unzip zip
        inputs:
          archiveFilePatterns: &#039;$(System.ArtifactsDirectory)/$(pipelineArtifact)/$(buildResultZipName)&#039;
          destinationFolder: &#039;$(System.ArtifactsDirectory)/BuildImage&#039;
          cleanDestinationFolder: true
          overwriteExistingFiles: true
      - task: Docker@2
        displayName: Build image
        inputs:
          repository: &#039;$(imgRepository)&#039;
          command: &#039;build&#039;
          Dockerfile: $(buildDockerfile)
          buildContext: &#039;$(System.ArtifactsDirectory)/BuildImage&#039;
          arguments: &#039;--no-cache&#039;
          tags: |
            latest
      - task: Docker@2
        displayName: &quot;Login to Container Registry&quot;
        inputs:
          command: login
          containerRegistry: $(imgRegistryService)
      - task: Bash@3
        displayName: Push docker image
        inputs:
          targetType: &#039;inline&#039;
          script: |
            docker push -a $(imgRepository)
  - job: DeployCloudRun
    dependsOn: BuildImage
    steps:
      - task: Bash@3
        displayName: Deploy docker image to cloudrun
        inputs:
          targetType: &#039;inline&#039;
          script: |
            docker run --rm \
            -v $(Build.SourcesDirectory)/$(gcpAuthJsonFile):/gcp/cloudKey.json \
            asia.gcr.io/google.com/cloudsdktool/google-cloud-cli:latest \
            bash -c &quot;gcloud auth login --cred-file=/gcp/cloudKey.json &amp;&amp; gcloud run deploy $(cloudRunServiceName) --set-env-vars=Ironman=$(Build.BuildId) --image $(imgRepository) --region $(cloudRunRegion) --project $(cloudRunProjectId) --allow-unauthenticated&quot;</code></pre>



<p>哇！一來就是一長串的YAML內容…</p>



<p>不不不，你如果是用VSCode打開，幾乎可以把前面兩個Job折疊起來，這邊增加的DeployCloudRun Job也只有一個Bash的task，不多的。</p>



<p>就讓我娓娓道來這篇主要增加的內容吧！</p>



<pre class="wp-block-prismatic-blocks"><code class="language-yaml">  cloudRunServiceName: ironmanweb
  cloudRunPort: 8080
  cloudRunRegion: asia-east1
  cloudRunProjectId: feisty-mechanic-363012
  gcpAuthJsonFile: ironman2022-gcp-key.json</code></pre>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" decoding="async" width="602" height="240" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/01-CloudRunList.png?resize=602%2C240&#038;ssl=1" alt="" class="wp-image-1936" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/01-CloudRunList.png?w=602&amp;ssl=1 602w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/01-CloudRunList.png?resize=300%2C120&amp;ssl=1 300w" sizes="(max-width: 602px) 100vw, 602px" /></figure></div>


<p>cloudRunServiceName就是圖中Cloud Run的名稱。</p>



<p>cloudRunPort設定為8080是在appsettings.json中設定了Kestrel的Http是使用8080 Port，也就是container內會監聽什麼Port，對應docker指令就是-p 80:8080。</p>



<p>cloudRunRegion則是CloudRun佈署的區域(機房)。</p>



<p>cloudRunProjectId可以直接從Google Cloud管理介面的URL得知，也就是在上圖畫面的時候，看一下瀏覽器上的網址列，「&amp;project=」後面的就是了，或是選擇Project的下拉選單：</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img data-recalc-dims="1" decoding="async" width="1024" height="399" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/02-GCPFindProjectId-1024x399.png?resize=1024%2C399&#038;ssl=1" alt="" class="wp-image-1938" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/02-GCPFindProjectId.png?resize=1024%2C399&amp;ssl=1 1024w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/02-GCPFindProjectId.png?resize=300%2C117&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/02-GCPFindProjectId.png?resize=768%2C299&amp;ssl=1 768w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/02-GCPFindProjectId.png?w=1142&amp;ssl=1 1142w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption>Google Cloud的Project Id</figcaption></figure></div>


<p>最後的gcpAuthJsonFile則是前面幾篇用來授權的Json檔案。不過這邊要補充一下，<a href="https://tech.uccu.website/2022ironman-day4-create-registry-and-service-account.html" target="_blank" rel="noreferrer noopener">那時候</a>在新增服務帳戶的時候還少加了一個「<strong>服務帳戶使用者</strong>」角色，所以漏加這個角色繼續做下去的話，就會碰到下面的錯誤訊息：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p></p><cite>PERMISSION_DENIED: Permission &#8216;iam.serviceaccounts.actAs&#8217; denied on service account</cite></blockquote>



<p>為了讓後面使用gcloud cli可以順利執行，所以要先在<a href="https://console.cloud.google.com/iam-admin/iam?hl=zh-TW" target="_blank" rel="noreferrer noopener">IAM</a>裡面將前面新增的服務帳戶加上「<strong>服務帳戶使用者</strong>」角色：</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="424" height="340" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/03-AddGCPIAMRole.png?resize=424%2C340&#038;ssl=1" alt="" class="wp-image-1939" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/03-AddGCPIAMRole.png?w=424&amp;ssl=1 424w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/03-AddGCPIAMRole.png?resize=300%2C241&amp;ssl=1 300w" sizes="auto, (max-width: 424px) 100vw, 424px" /><figcaption>新增「<strong>服務帳戶使用者</strong>」角色</figcaption></figure></div>


<p>增加角色之後不需要重新下載用於授權的Json檔案，因為有什麼角色權限不會寫在檔案裡。</p>



<p>Job越加越多，這三個Job之間其實是有相依性的，也就是說要先BuildCode之後才能夠BuildImage，接下來才能DeployCloudRun，所以在第二個和第三個Job底下分別要加上dependsOn的屬性：</p>



<pre class="wp-block-prismatic-blocks"><code class="language-yaml">- job: BuildImage
    dependsOn: BuildCode</code></pre>



<pre class="wp-block-prismatic-blocks"><code class="language-yaml">- job: DeployCloudRun
    dependsOn: BuildImage</code></pre>



<p>這個部份滿重要的，尤其是如果有<a href="https://azure.microsoft.com/zh-tw/pricing/details/devops/azure-devops-services/" target="_blank" rel="noreferrer noopener">額外購買CloudAgent的執行數量</a>時，因為Job可以在不同的Agent執行，所以它可以在同一個Pipeline同時跑多個Job(沒有設定相依的dependsOn時)，就算沒有額外購買CloudAgent的執行數量，沒設定dependsOn也無法保證它們的執行順序。BuildImage的Job相較前一篇有增加的也只有dependsOn這個屬性。</p>



<p>最後就是DeployCloudRun這個Job，裡面的內容也只有一個Bash的task，所以下面我直接貼bash script的部份：</p>



<pre class="wp-block-prismatic-blocks"><code class="language-bash">            docker run --rm \
            -v $(Build.SourcesDirectory)/$(gcpAuthJsonFile):/gcp/cloudKey.json \
            asia.gcr.io/google.com/cloudsdktool/google-cloud-cli:latest \
            bash -c &quot;gcloud auth login --cred-file=/gcp/cloudKey.json &amp;&amp; gcloud run deploy $(cloudRunServiceName) --set-env-vars=Ironman=$(Build.BuildId) --image $(imgRepository) --region $(cloudRunRegion) --project $(cloudRunProjectId) --allow-unauthenticated&quot;</code></pre>



<p>在這裡是使用google的gcloud CLI工具來執行CloudRun的佈署，不過gcloud CLI工具還是需要安裝的，要嘛是需要先裝在Agent的電腦內，而且還要去爬<a href="https://cloud.google.com/sdk/docs/install" target="_blank" rel="noreferrer noopener">官方的安裝文件</a>知道怎麼安裝，不然就是安裝在Docker Image裡面。</p>



<p>我們使用的是ClougAgent，所以Agent的環境不是我們可以控制的，每次執行也是新的vm執行起來，所以選擇後者使用google建立的gcloud CLI的Docker Image會是最理想的選擇，除了從<a href="https://hub.docker.com/r/google/cloud-sdk" target="_blank" rel="noreferrer noopener">Docker Hub</a>可以找到之外，<a href="https://cloud.google.com/sdk/docs/downloads-docker" target="_blank" rel="noreferrer noopener">官方文件</a>也有提供不同Container Registry的選擇說明。</p>



<p>使用Container來執行gcloud CLI，我們可以省去安裝的麻煩事，只要會使用就可以了，這讓我們可以更專注在其它的設計部份。</p>



<p>script中的重點只有第二行和最後一行，分別是把授權用的Json檔案關聯到Container裡面，以便讓裡面的gcloud CLI工具可以讀取到內容進行login動作，以及最後一行包含lgoin的指令。</p>



<p>最後一行的指令有個重點，就是我們必須先使用gloud auth login的指令讓CLI工具登入，接著才能執行CloudRun的Deploy指令，也就是說要執行的指令有兩個，所以使用了「&amp;&amp;」這個管道符號讓它接續執行，但是直接這樣接在Image Repository後面是行不通的，「&amp;&amp;」符號的前面會跟最前面的docker run指令合起來作為第一個指令，後面的則是host環境接續docker run指令執行的第二個指令。</p>



<p>所以在這個地方必須是讓docker run執行起來的contianer是執行bash程式，後面接著要執行的指令字串(用引號包起來)，也因為是一整個字串，所以沒辦法使用「\\」換行，就會是一行很長的指令，下面為了方便閱讀，把它們拆開來說明。</p>



<pre class="wp-block-prismatic-blocks"><code class="language-bash">gcloud auth login --cred-file=/gcp/cloudKey.json</code></pre>



<p>「&amp;&amp;」符號前的這一行是將gcloud CLI工具登入，使用&#8211;cred-file參數帶入Json檔案，後面的路徑是Container內的路徑，也就是前面-v設定的部份。</p>



<pre class="wp-block-prismatic-blocks"><code class="language-batch">gcloud run deploy $(cloudRunServiceName) --set-env-vars=Ironman=$(Build.BuildId) --image $(imgRepository) --region $(cloudRunRegion) --project $(cloudRunProjectId) --allow-unauthenticated</code></pre>



<p>「&amp;&amp;」符號後面的指令我依參數拆行來看應該就很清楚，因為大部份都是上面設定的變數，已經有說明過了。</p>



<p>&#8211;set-env-vars的參數是設定CloudRun放入的環境變數(還記得前面的Ironman環境變數嗎？)，&#8211;allow-unauthenticated則是允許訪客瀏覽，不然CloudRun可能不會正常回應頁面。</p>



<p>gcloud run deploy $(cloudRunServiceName) <br>&#8211;set-env-vars=Ironman=$(Build.BuildId) <br>&#8211;image $(imgRepository) <br>&#8211;region $(cloudRunRegion) <br>&#8211;project $(cloudRunProjectId) <br>&#8211;allow-unauthenticated</p>



<p>關於CloudRun在gcloud CLI可以設定的更多參數部份，請參考<a href="https://cloud.google.com/sdk/gcloud/reference/run/deploy" target="_blank" rel="noreferrer noopener">官方文件的頁面</a>，之後的文章還會把部份參數用上。</p>



<p>最後，在CI Pipeline成功執行完之後，就可以在對應的Task log中看到gcloud CLI工具吐出來的CloudRun網址，這樣就不用進入到GCP的管理介面去查看。</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="689" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/04-DeployCloudRunLog-1024x689.png?resize=1024%2C689&#038;ssl=1" alt="" class="wp-image-1940" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/04-DeployCloudRunLog.png?resize=1024%2C689&amp;ssl=1 1024w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/04-DeployCloudRunLog.png?resize=300%2C202&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/04-DeployCloudRunLog.png?resize=768%2C516&amp;ssl=1 768w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/04-DeployCloudRunLog.png?w=1316&amp;ssl=1 1316w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure></div>


<p>最後補充一個小細節，就是在這三個Job之中，只有BuildCode這個Job中有明確的加上checkout: sources，並且身份驗證的Json檔案是放在Pipelines這個Git Repository裡面，但是在後面的BuildImage和DeployCloudRun並沒有明確加上checkout動作卻會(可以)取得Pipelines這個Git Repository裡面的檔案，也沒有在resources.repositories底下設定Pipelines，主要是因為這裡的Pipeline YAML檔案就是放在Pipelines這個Git Repository裡面，所以隱含了checkout: self這個動作，替我們省下了一些設定。(下圖紅框與藍框的差異)</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="336" height="1024" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/05-DefaultCheckoutSelfRepo-336x1024.png?resize=336%2C1024&#038;ssl=1" alt="" class="wp-image-1941" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/05-DefaultCheckoutSelfRepo.png?resize=336%2C1024&amp;ssl=1 336w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/05-DefaultCheckoutSelfRepo.png?resize=98%2C300&amp;ssl=1 98w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/05-DefaultCheckoutSelfRepo.png?resize=504%2C1536&amp;ssl=1 504w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/05-DefaultCheckoutSelfRepo.png?w=547&amp;ssl=1 547w" sizes="auto, (max-width: 336px) 100vw, 336px" /></figure></div><p>這篇文章 <a href="https://tech.uccu.website/2022ironman-day10-ci-pipeline-deploy-dev-to-cloudrun.html">【2022鐵人賽】基本版-建立CI Pipeline(3)</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tech.uccu.website/2022ironman-day10-ci-pipeline-deploy-dev-to-cloudrun.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1930</post-id>	</item>
		<item>
		<title>【2022鐵人賽】基本版-建立CI Pipeline(2)</title>
		<link>https://tech.uccu.website/2022ironman-day9-ci-pipeline-buildimage.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=2022ironman-day9-ci-pipeline-buildimage</link>
					<comments>https://tech.uccu.website/2022ironman-day9-ci-pipeline-buildimage.html#respond</comments>
		
		<dc:creator><![CDATA[鳴人]]></dc:creator>
		<pubDate>Sat, 24 Sep 2022 15:05:59 +0000</pubDate>
				<category><![CDATA[2022鐵人賽]]></category>
		<category><![CDATA[Azure DevOps]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[iThome鐵人賽]]></category>
		<category><![CDATA[2022ironman]]></category>
		<category><![CDATA[azure devops]]></category>
		<category><![CDATA[docker]]></category>
		<guid isPermaLink="false">https://tech.uccu.website/?p=1923</guid>

					<description><![CDATA[<p>前一篇文章提到CI Pipeline的規劃包含Build Code、Build Image、Deploy De ... <a title="【2022鐵人賽】基本版-建立CI Pipeline(2)" class="read-more" href="https://tech.uccu.website/2022ironman-day9-ci-pipeline-buildimage.html" aria-label="Read more about 【2022鐵人賽】基本版-建立CI Pipeline(2)">閱讀全文</a></p>
<p>這篇文章 <a href="https://tech.uccu.website/2022ironman-day9-ci-pipeline-buildimage.html">【2022鐵人賽】基本版-建立CI Pipeline(2)</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p><a href="https://tech.uccu.website/2022ironman-day8-ci-pipeline-buildcode.html" target="_blank" rel="noreferrer noopener">前一篇文章</a>提到CI Pipeline的規劃包含Build Code、Build Image、Deploy Dev to CloudRun，這一篇就來處理Build Image的部份，直接先看完成的YAML吧！</p>



<pre class="wp-block-prismatic-blocks"><code class="language-yaml">trigger:
- none

pool:
  vmImage: ubuntu-latest

resources:
  repositories:
  - repository: sources
    type: git
    name: ironman2022/NetApp
    ref: Develop
    trigger:
      branches:
        include:
          - Develop

variables:
  pipelineArtifact: output
  buildResultZipName: buildResult.zip
  slnOrCsprojName: IronmanWeb.sln
  imgRepository: &#039;asia-east1-docker.pkg.dev/feisty-mechanic-363012/ironman2022/ironmanweb&#039;
  buildDockerfile: &#039;Dockerfile&#039;
  imgRegistryService: &#039;GCPArtifactRegistry&#039;

jobs:
  - job: BuildCode
    steps:
      - checkout: sources
        clean: true
      - script: |
          export UID=$(id -u)
          export GID=$(id -g)
          docker run --user $UID:$GID --rm \
          -v $(Build.SourcesDirectory):/tmp/source \
          -v $(Build.BinariesDirectory):/tmp/publish \
          -e DOTNET_CLI_HOME=/tmp/.dotnet \
          mcr.microsoft.com/dotnet/sdk:6.0-alpine \
          dotnet publish /tmp/source/$(slnOrCsprojName) \
          -c release \
          -o /tmp/publish
        displayName: Dotnet Build
      - task: ArchiveFiles@2
        displayName: 壓縮成zip
        inputs:
          rootFolderOrFile: $(Build.BinariesDirectory)
          includeRootFolder: false
          archiveType: &#039;zip&#039;
          archiveFile: &#039;$(Build.ArtifactStagingDirectory)/zipFiles/$(buildResultZipName)&#039;
          replaceExistingArchive: true
      - task: PublishBuildArtifacts@1
        displayName: 上傳到Pipeline Artifact
        inputs:
          PathtoPublish: &#039;$(Build.ArtifactStagingDirectory)/zipFiles/$(buildResultZipName)&#039;
          ArtifactName: &#039;$(pipelineArtifact)&#039;
          publishLocation: &#039;Container&#039;
  - job: BuildImage
    steps:
      - task: DownloadBuildArtifacts@0
        displayName: 下載Pipeline Artifact
        inputs:
          buildType: &#039;current&#039;
          cleanDestinationFolder: true
          downloadType: &#039;single&#039;
          artifactName: &#039;$(pipelineArtifact)&#039;
          downloadPath: &#039;$(System.ArtifactsDirectory)/&#039;
      - task: ExtractFiles@1
        displayName: Unzip zip
        inputs:
          archiveFilePatterns: &#039;$(System.ArtifactsDirectory)/$(pipelineArtifact)/$(buildResultZipName)&#039;
          destinationFolder: &#039;$(System.ArtifactsDirectory)/BuildImage&#039;
          cleanDestinationFolder: true
          overwriteExistingFiles: true
      - task: Docker@2
        displayName: Build image
        inputs:
          repository: &#039;$(imgRepository)&#039;
          command: &#039;build&#039;
          Dockerfile: $(buildDockerfile)
          buildContext: &#039;$(System.ArtifactsDirectory)/BuildImage&#039;
          arguments: &#039;--no-cache&#039;
          tags: |
            latest
      - task: Docker@2
        displayName: &quot;Login to Container Registry&quot;
        inputs:
          command: login
          containerRegistry: $(imgRegistryService)
      - task: Bash@3
        displayName: Push docker image
        inputs:
          targetType: &#039;inline&#039;
          script: |
            docker push -a $(imgRepository)</code></pre>



<p>今天的YAML內容雖然是沿用前一篇的內容，但是從resources之後就有些改變。首先可以看到在resources之後加上了variables的區段，裡面設定了幾個不同的變數。</p>



<p>前三個是從昨天的BuildCode job中將一些內容抽離出來改為變數，除了方便在一開始設定並且若有用在不只一個地方而有修改的話只要改變數內容就不用擔心漏改。</p>



<p>後三個就是這一篇BuildImage會使用到的變數，就是Image Repository、Dockerfile以及在Azure DevOps設定的Service Connection名稱。</p>



<pre class="wp-block-prismatic-blocks"><code class="language-">variables:
  pipelineArtifact: output
  buildResultZipName: buildResult.zip
  slnOrCsprojName: IronmanWeb.sln
  imgRepository: &#039;asia-east1-docker.pkg.dev/feisty-mechanic-363012/ironman2022/ironmanweb&#039;
  buildDockerfile: &#039;Dockerfile&#039;
  imgRegistryService: &#039;GCPArtifactRegistry&#039;</code></pre>



<p>為了要在Build完Docker Image之後推到GCP的Artifact Registry，所以需要建立一個Service Connection來存放身份驗證的設定，主要是這邊使用到的是內建的Docker task。還記得<a href="https://tech.uccu.website/2022ironman-day4-create-registry-and-service-account.html" target="_blank" rel="noreferrer noopener">前面文章</a>的json檔案嗎？又需要它的內容囉！檔案要好好保管好啊，後面還有它再度出場的時候。</p>



<p>建立Service Connection從Azure DevOps的專案設定(Project Settings)進入，按下「Create service connection」按鈕之後選擇「Docker Registry」的選項，接著要輸入的內容就和<a href="https://tech.uccu.website/2022ironman-day4-create-registry-and-service-account.html" target="_blank" rel="noreferrer noopener">前面文章</a>的最後面Docker Login差不多，而Docker Password的內容就把json檔案的內容全部複製之後貼進去就行了(我知道，有點長)，最重要的就是記下Service connection name，回來貼到上面的imgRegistryService變數中。</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="923" height="684" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/ServiceConnection-Empty.png?resize=923%2C684&#038;ssl=1" alt="" class="wp-image-1925" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/ServiceConnection-Empty.png?w=923&amp;ssl=1 923w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/ServiceConnection-Empty.png?resize=300%2C222&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/ServiceConnection-Empty.png?resize=768%2C569&amp;ssl=1 768w" sizes="auto, (max-width: 923px) 100vw, 923px" /></figure></div>

<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="453" height="716" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/SelectNewServiceConnectionType.png?resize=453%2C716&#038;ssl=1" alt="" class="wp-image-1926" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/SelectNewServiceConnectionType.png?w=453&amp;ssl=1 453w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/SelectNewServiceConnectionType.png?resize=190%2C300&amp;ssl=1 190w" sizes="auto, (max-width: 453px) 100vw, 453px" /></figure></div>

<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="444" height="738" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/NewDockerRegistryServiceConnectionDetails.png?resize=444%2C738&#038;ssl=1" alt="" class="wp-image-1927" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/NewDockerRegistryServiceConnectionDetails.png?w=444&amp;ssl=1 444w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/NewDockerRegistryServiceConnectionDetails.png?resize=180%2C300&amp;ssl=1 180w" sizes="auto, (max-width: 444px) 100vw, 444px" /></figure></div>


<p>對了，上面的buildDockerfile變數值是Dockerfile，這個dockerfile的內容如下，如果你儲存的檔名並不是使用預設的Dockerfile這個名稱，記得變數值要改喔！</p>



<pre class="wp-block-prismatic-blocks"><code class="language-">FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine

WORKDIR /app
COPY . .

ENTRYPOINT [&quot;dotnet&quot;, &quot;IronmanWeb.dll&quot;]</code></pre>



<p>上面這個Dockerfile沒什麼特別，除了第一行使用的Image版本選用alpine這個比較小的Image之外，再來就是要執行dll名稱直接寫在裡面。</p>



<p>嗯？可以不這樣寫在裡面嗎？後面的文章就會知道了…</p>



<p>接下來第一個Job是<a href="https://tech.uccu.website/2022ironman-day8-ci-pipeline-buildcode.html" target="_blank" rel="noreferrer noopener">前一篇</a>的BuildCode Job，裡面的內容可以自己對照一下，把哪些小地方改成變數。</p>



<p>這篇的內容主要在第二個job，也就是BuildImage的部份。因為前一個BuildCode job裡面把編譯的結果都壓縮成zip，而且這邊是分開在兩個不同的Job執行後續的動作，所以在第二個Job需要先有一個下載Pipeline Artifact的動作，主要是不同Job其實是可以在不同的Agent執行，去年的<a href="https://tech.uccu.website/2021ironman-day27-defined-multiple-jobs-in-yaml.html" target="_blank" rel="noreferrer noopener">這一篇文章</a>有介紹過。</p>



<pre class="wp-block-prismatic-blocks"><code class="language-yaml">      - task: DownloadBuildArtifacts@0
        displayName: 下載Pipeline Artifact
        inputs:
          buildType: &#039;current&#039;
          cleanDestinationFolder: true
          downloadType: &#039;single&#039;
          artifactName: &#039;$(pipelineArtifact)&#039;
          downloadPath: &#039;$(System.ArtifactsDirectory)/&#039;</code></pre>



<p>下載了前面Job上傳到Pipeline Artifact中的zip檔案之後，接下來當然是要把它解壓縮之後才能使用，解壓縮到$(System.ArtifactsDirectory)/BuildImage這個位置，這個位置會在後面用於buildContext的位置。</p>



<pre class="wp-block-prismatic-blocks"><code class="language-">      - task: ExtractFiles@1
        displayName: Unzip zip
        inputs:
          archiveFilePatterns: &#039;$(System.ArtifactsDirectory)/$(pipelineArtifact)/$(buildResultZipName)&#039;
          destinationFolder: &#039;$(System.ArtifactsDirectory)/BuildImage&#039;
          cleanDestinationFolder: true
          overwriteExistingFiles: true</code></pre>



<p>接下來最後面的三個Task都是和Docker相關的動作，分別是Build、Login、Push。</p>



<pre class="wp-block-prismatic-blocks"><code class="language-">      - task: Docker@2
        displayName: Build image
        inputs:
          repository: &#039;$(imgRepository)&#039;
          command: &#039;build&#039;
          Dockerfile: $(buildDockerfile)
          buildContext: &#039;$(System.ArtifactsDirectory)/BuildImage&#039;
          arguments: &#039;--no-cache&#039;
          tags: |
            latest
      - task: Docker@2
        displayName: &quot;Login to Container Registry&quot;
        inputs:
          command: login
          containerRegistry: $(imgRegistryService)
      - task: Bash@3
        displayName: Push docker image
        inputs:
          targetType: &#039;inline&#039;
          script: |
            docker push -a $(imgRepository)</code></pre>



<p>第一個Task是使用Docker task並且command設定為build，tags的部份是陣列類型，如果你建立的Image不只要使用latest為tag，那就多加一行，每一行一個tag。</p>



<p>第二個Task就是使用上面設定的Service Connection名稱執行登入的動作，如果不是透過Service Connection的話，就必須自己透過指令登入，那就會額外需要json檔案，比較麻煩一點，所以這邊先簡單做吧！</p>



<p>第三個Task使用的是Bash執行docker push的指令，其實和第一個Task一樣用Docker task也是可以，在command的部份改成push就行(利用UI的Task小助手方便設定)，只是用Bash執行docker push指令也不難，剛好可以對照做個比較。</p>



<p>到這邊為止，已經將程式編譯和建置Docker Image的動作都完成了，可以試試在本機端把Image拉下來執行看看有沒有問題喔！</p>



<p>下一篇將會使用到gcloud cli工具佈署Dev環境的CloudRun，下回見。</p>
<p>這篇文章 <a href="https://tech.uccu.website/2022ironman-day9-ci-pipeline-buildimage.html">【2022鐵人賽】基本版-建立CI Pipeline(2)</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tech.uccu.website/2022ironman-day9-ci-pipeline-buildimage.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1923</post-id>	</item>
		<item>
		<title>【2022鐵人賽】基本版-建立CI Pipeline(1)</title>
		<link>https://tech.uccu.website/2022ironman-day8-ci-pipeline-buildcode.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=2022ironman-day8-ci-pipeline-buildcode</link>
					<comments>https://tech.uccu.website/2022ironman-day8-ci-pipeline-buildcode.html#respond</comments>
		
		<dc:creator><![CDATA[鳴人]]></dc:creator>
		<pubDate>Fri, 23 Sep 2022 15:23:52 +0000</pubDate>
				<category><![CDATA[2022鐵人賽]]></category>
		<category><![CDATA[Azure DevOps]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[iThome鐵人賽]]></category>
		<category><![CDATA[2022ironman]]></category>
		<category><![CDATA[azure devops]]></category>
		<category><![CDATA[docker]]></category>
		<guid isPermaLink="false">https://tech.uccu.website/?p=1919</guid>

					<description><![CDATA[<p>CI Pipeline包含了Build Code、Build Image、Deploy Dev這三個部份，這篇先處理Build code，但不使用內建的task來編譯程式的設計方式，改用dotnet sdk的docker image來處理編譯的這一塊，後續內容則會在後面的文章介紹。</p>
<p>這篇文章 <a href="https://tech.uccu.website/2022ironman-day8-ci-pipeline-buildcode.html">【2022鐵人賽】基本版-建立CI Pipeline(1)</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p>昨天建立完PR Pipeline之後，接下來就需要建立CI Pipeline來編譯.Net程式了，不過CI Pipeline要做的事情比較多，包含Build Code、Build Image、Deploy Dev to CloudRun，所以這些部份會分幾篇文章來完成。</p>



<p>首先是Build Code的部份，雖然<a href="https://tech.uccu.website/2021ironman-day7-first-pipeline-template-and-editor.html" target="_blank" rel="noreferrer noopener">去年的文章</a>已經有建立YAML的Pipeline來編譯.Net程式的範例，但是今年還是要再做一次。</p>



<p>只是如果我只是照著去年的內容搬過來改一小部份就交差，那肯定會被噓爆的吧？所以這次我們改用.net sdk的docker image執行container來編譯.net的程式，這樣就是新鮮貨了，對吧！</p>



<p>因為用container執行sdk是透過script的方式執行，所以就沒辦法使用去年文章的方式把小助手叫出來設定幾個選項搞定，能透過小幫手的部份也只是加一個Bash的Task，差異不大。不囉嗦，還是直接先上code吧！</p>



<pre class="wp-block-prismatic-blocks"><code class="language-yaml">trigger:
- none

pool:
  vmImage: ubuntu-latest

resources:
  repositories:
  - repository: sources
    type: git
    name: ironman2022/NetApp
    ref: Develop
    trigger:
      branches:
        include:
          - Develop

jobs:
  - job: BuildCode
    steps:
      - checkout: sources
        clean: true
      - script: |
          export UID=$(id -u)
          export GID=$(id -g)
          docker run --user $UID:$GID --rm \
          -v $(Build.SourcesDirectory):/tmp/source \
          -v $(Build.BinariesDirectory):/tmp/publish \
          -e DOTNET_CLI_HOME=/tmp/.dotnet \
          mcr.microsoft.com/dotnet/sdk:6.0-alpine \
          dotnet publish /tmp/source/IronmanWeb.sln \
          -c release \
          -o /tmp/publish
        displayName: Dotnet Build
      - task: ArchiveFiles@2
        displayName: 壓縮成zip
        inputs:
          rootFolderOrFile: $(Build.BinariesDirectory)
          includeRootFolder: false
          archiveType: &#039;zip&#039;
          archiveFile: &#039;$(Build.ArtifactStagingDirectory)/zipFiles/buildResult.zip&#039;
          replaceExistingArchive: true
      - task: PublishBuildArtifacts@1
        displayName: 上傳到Pipeline Artifact
        inputs:
          PathtoPublish: &#039;$(Build.ArtifactStagingDirectory)/zipFiles/buildResult.zip&#039;
          ArtifactName: &#039;output&#039;
          publishLocation: &#039;Container&#039;</code></pre>



<p>上面的YAML內容前半段是借用<a href="https://tech.uccu.website/2022ironman-day7-create-pr-pipeline.html" target="_blank" rel="noreferrer noopener">前一篇文章</a>的設定，從sources這個repository的ref分支參考的下一行開始才是今天新增的內容。可以發現多增加了trigger的部份，而且是設定在resources底下的repository設定，同時設定了以Develop分支為篩選條件(branches下面的include)。</p>



<pre class="wp-block-prismatic-blocks"><code class="language-yaml">    trigger:
      branches:
        include:
          - Develop</code></pre>



<p>接著下面有一個名為BuildCode的Job，這個名稱不是放在displayName屬性裡面，它是屬於ID(也就是識別)的一種，只能包含英數字和底線，不能有空格也不能以數字為開頭。簡單來說就是程式的變數命名的概念。</p>



<pre class="wp-block-prismatic-blocks"><code class="language-yaml">jobs:
  - job: BuildCode</code></pre>



<p>接著在BuildCode Job底下的steps加入了許多不同的task，先checkout程式碼出來，沒有特別設定path的話會放在Build.SourcesDirectory這個<a href="https://learn.microsoft.com/zh-tw/azure/devops/pipelines/build/variables?view=azure-devops&amp;tabs=yaml" target="_blank" rel="noreferrer noopener">系統預先定義的變數</a>中。</p>



<p>接著我們使用script(等同Bash task)寫了一段Linux的script，利用dotnet 6.0的<a href="https://hub.docker.com/_/microsoft-dotnet-sdk" target="_blank" rel="noreferrer noopener">sdk docker image</a>(使用alpine的版本，image size比較小)建立container來編譯.net程式(使用publish命令)。</p>



<p>在script中每一行最後的空白加上「\」符號是命令區塊換行的用法，是為了可讀性，實際執行的時候會是與同一行指令相同效果。</p>



<pre class="wp-block-prismatic-blocks"><code class="language-yaml">      - script: |
          export UID=$(id -u)
          export GID=$(id -g)
          docker run --user $UID:$GID --rm \
          -v $(Build.SourcesDirectory):/tmp/source \
          -v $(Build.BinariesDirectory):/tmp/publish \
          -e DOTNET_CLI_HOME=/tmp/.dotnet \
          mcr.microsoft.com/dotnet/sdk:6.0-alpine \
          dotnet publish /tmp/source/IronmanWeb.sln \
          -c release \
          -o /tmp/publish
        displayName: Dotnet Build</code></pre>



<p>前面兩行的export是為了將host的uid和gid帶到container裡面(&#8211;user $UID:$GID部份)，避免檔案建立之後的問題。</p>



<p>&#8211;rm是告訴docker當container使用完就可以刪掉了。</p>



<p>-v的部份則是掛volume的設定，也就是將host的路徑與container內的路徑關聯起來，script中的例子就是把$(Build.SourcesDirectory)所代表的位置和$(Build.BinariesDirectory)所代表的位置分別和container內的/tmp底下的source、publish目錄關聯起來，這樣就可以在source目錄中找到程式碼，編譯產生的檔案放在container內的publish目錄則是相當於放在host。</p>



<p>-e DOTNET_CLI_HOME=/tmp/.dotnet這部份也是把sdk的Home目錄設在/tmp底下的.dotnet目錄，會發現這裡都是使用/tmp開頭，因為在這個tmp目錄下才不會碰到存取權限的問題。</p>



<p>在mcr.microsoft.com/dotnet/sdk:6.0-alpine這個image repository之後就是單純的dotnet sdk的指令，和docker container就沒什麼關係了，就是指定使用publish指令與其它設定來執行編譯的動作。</p>



<p>接下來後面兩個task就是把編譯完的檔案壓縮成zip檔案之後上傳到Pipeline Run的Artifacts空間中，這部份和<a href="https://tech.uccu.website/2021ironman-day9-build-consoleapp-and-publish-artifacts.html" target="_blank" rel="noreferrer noopener">去年的這篇文章內容</a>做的事是一樣的。</p>



<p>因此這篇內容的關鍵在於resources的repository設定與利用docker container來編譯程式而不是使用內建的dotnet task。</p>



<p>到這邊就完成了Build Code的設計部份，後續步驟請待下一篇文章分享。</p>
<p>這篇文章 <a href="https://tech.uccu.website/2022ironman-day8-ci-pipeline-buildcode.html">【2022鐵人賽】基本版-建立CI Pipeline(1)</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tech.uccu.website/2022ironman-day8-ci-pipeline-buildcode.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1919</post-id>	</item>
		<item>
		<title>【2022鐵人賽】Docker Image存放位置選擇：Google Artifact Registry</title>
		<link>https://tech.uccu.website/2022ironman-day4-create-registry-and-service-account.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=2022ironman-day4-create-registry-and-service-account</link>
					<comments>https://tech.uccu.website/2022ironman-day4-create-registry-and-service-account.html#respond</comments>
		
		<dc:creator><![CDATA[鳴人]]></dc:creator>
		<pubDate>Mon, 19 Sep 2022 15:27:50 +0000</pubDate>
				<category><![CDATA[2022鐵人賽]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Google雲端GCP]]></category>
		<category><![CDATA[iThome鐵人賽]]></category>
		<category><![CDATA[2022ironman]]></category>
		<category><![CDATA[docker]]></category>
		<guid isPermaLink="false">https://tech.uccu.website/?p=1855</guid>

					<description><![CDATA[<p>上一篇「流程規劃說明」有提到在CI Pipeline的時候我們會Build docker image，所以自然 ... <a title="【2022鐵人賽】Docker Image存放位置選擇：Google Artifact Registry" class="read-more" href="https://tech.uccu.website/2022ironman-day4-create-registry-and-service-account.html" aria-label="Read more about 【2022鐵人賽】Docker Image存放位置選擇：Google Artifact Registry">閱讀全文</a></p>
<p>這篇文章 <a href="https://tech.uccu.website/2022ironman-day4-create-registry-and-service-account.html">【2022鐵人賽】Docker Image存放位置選擇：Google Artifact Registry</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p>上一篇「<a href="https://tech.uccu.website/2022ironman-day3-flow-plan.html" target="_blank" rel="noreferrer noopener">流程規劃說明</a>」有提到在CI Pipeline的時候我們會Build docker image，所以自然也就要幫Image找尋存放的位置囉！</p>



<p>Docker Image存放位置的選擇其實也很多，像是放在Docker Hub、Azure Container Registry(ACR)、Google Container Registry(GCR)/Google Artifact Registry(GAR)、Amazon Elastic Container Registry(ECR)等等…，這些雲端服務都是可以選擇的選項。</p>



<p>不過，因為我們後面使用的是Google的Cloud Run，所以為了有最佳的網路傳輸與最節省成本的考量(主要還是網路輸出的費用部份)，所以就直接選擇放在Google的雲端服務上面。</p>



<p>然而，Google現在可以存放Docker Image的服務其實有兩個，就像上面寫的，有分為Google Container Registry和Google Artifact Registry，這兩者其實對於存放Docker Image沒有什麼太大的差別，主要應該只是URI的部份不一樣。如果真要說的話，GCR就如同它的名稱一樣，就是拿來放Image的，而GAR則是綜合存放庫的服務，除了可以放Image之外，也可以是程式的套件庫，就像.Net的Nuget套件庫一樣。</p>



<p>GAR是由GCR演進而來(官網就是這麼寫的)，所以GAR是新的服務，會不會在之後取代GCR很難說…，所以在裡面既然要建立新的存放空間，那就直接從新的服務建立吧！</p>



<h2 class="wp-block-heading">建立Artifact Registry存放區</h2>



<p>進到Google Cloud的管理頁面之後，從左邊的選單找到「Artifact Registry」吧(或是你想要<a href="https://console.cloud.google.com/artifacts?hl=zh-TW" target="_blank" rel="noreferrer noopener">任意門</a>)！如果找不到的話，應該是被隱藏在最下面的「其他產品」裡面。</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="384" height="1024" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-Menu-OtherProducts-384x1024.png?resize=384%2C1024&#038;ssl=1" alt="" class="wp-image-1856" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-Menu-OtherProducts.png?resize=384%2C1024&amp;ssl=1 384w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-Menu-OtherProducts.png?resize=113%2C300&amp;ssl=1 113w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-Menu-OtherProducts.png?w=489&amp;ssl=1 489w" sizes="auto, (max-width: 384px) 100vw, 384px" /></figure></div>

<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="509" height="417" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-Menu-ArtifactRegistry.png?resize=509%2C417&#038;ssl=1" alt="" class="wp-image-1857" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-Menu-ArtifactRegistry.png?w=509&amp;ssl=1 509w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-Menu-ArtifactRegistry.png?resize=300%2C246&amp;ssl=1 300w" sizes="auto, (max-width: 509px) 100vw, 509px" /></figure></div>


<p>進入Artifact Registry之後，找到「建立存放區」，接著填寫簡單的資料就可以建立一個新的存放區了，記得格式要選「Docker」，地區的部份輸入「asia」就會篩選出台灣的選項了。</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="717" height="155" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-CreateStorage.png?resize=717%2C155&#038;ssl=1" alt="" class="wp-image-1858" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-CreateStorage.png?w=717&amp;ssl=1 717w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-CreateStorage.png?resize=300%2C65&amp;ssl=1 300w" sizes="auto, (max-width: 717px) 100vw, 717px" /></figure></div>

<div class="wp-block-image">
<figure class="aligncenter size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="660" height="1024" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-CreateStorage-Details-660x1024.png?resize=660%2C1024&#038;ssl=1" alt="" class="wp-image-1859" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-CreateStorage-Details.png?resize=660%2C1024&amp;ssl=1 660w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-CreateStorage-Details.png?resize=193%2C300&amp;ssl=1 193w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-CreateStorage-Details.png?resize=768%2C1192&amp;ssl=1 768w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-CreateStorage-Details.png?w=841&amp;ssl=1 841w" sizes="auto, (max-width: 660px) 100vw, 660px" /></figure></div>


<p>建立完之後從列表中就可以看到剛才建立的項目，點進去之後就會看到Image Repository的URI了，旁邊很貼心的放了一個直接複製的按鈕。</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><a href="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-StorageList.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="108" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-StorageList-1024x108.png?resize=1024%2C108&#038;ssl=1" alt="" class="wp-image-1860" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-StorageList.png?resize=1024%2C108&amp;ssl=1 1024w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-StorageList.png?resize=300%2C32&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-StorageList.png?resize=768%2C81&amp;ssl=1 768w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-StorageList.png?resize=1536%2C161&amp;ssl=1 1536w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-StorageList.png?w=1694&amp;ssl=1 1694w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></a></figure></div>

<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="829" height="168" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-CopyURI.png?resize=829%2C168&#038;ssl=1" alt="" class="wp-image-1861" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-CopyURI.png?w=829&amp;ssl=1 829w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-CopyURI.png?resize=300%2C61&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/GCP-ArtifactRegistry-CopyURI.png?resize=768%2C156&amp;ssl=1 768w" sizes="auto, (max-width: 829px) 100vw, 829px" /></figure></div>


<p>到這邊雖然已經將存放Image的Artifact Registry存放區建立好，但是其實還有一個很重要的事情，那就是身份驗證與授權的部份。如果沒有登入的話是沒辦法將Image推送上來的，所以我們還需要建立對應的服務帳戶。</p>



<h2 class="wp-block-heading">建立服務帳戶</h2>



<p>同樣在Google Cloud左邊的Menu找到「IAM與管理」(<a href="https://console.cloud.google.com/iam-admin/iam?hl=zh-TW" target="_blank" rel="noreferrer noopener">任意門在此</a>)，進入之後點選左邊的「服務帳戶」，然後在右邊的上方點擊「建立服務帳戶」。</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="968" height="575" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount.png?resize=968%2C575&#038;ssl=1" alt="" class="wp-image-1862" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount.png?w=968&amp;ssl=1 968w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount.png?resize=300%2C178&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount.png?resize=768%2C456&amp;ssl=1 768w" sizes="auto, (max-width: 968px) 100vw, 968px" /></figure></div>


<p>需要填寫的步驟不多，不過在第二步的部份雖然它是寫「選用」，不過我們還是直接把後續會需要使用到的角色權限設定給它吧！分別是「Artifact Registry 寫入者」、「Cloud Run 開發人員」。</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="863" height="917" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount-Details1.png?resize=863%2C917&#038;ssl=1" alt="" class="wp-image-1863" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount-Details1.png?w=863&amp;ssl=1 863w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount-Details1.png?resize=282%2C300&amp;ssl=1 282w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount-Details1.png?resize=768%2C816&amp;ssl=1 768w" sizes="auto, (max-width: 863px) 100vw, 863px" /></figure></div>

<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="726" height="728" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount-Details2.png?resize=726%2C728&#038;ssl=1" alt="" class="wp-image-1864" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount-Details2.png?w=726&amp;ssl=1 726w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount-Details2.png?resize=300%2C300&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount-Details2.png?resize=150%2C150&amp;ssl=1 150w" sizes="auto, (max-width: 726px) 100vw, 726px" /></figure></div>

<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="809" height="841" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount-Details3.png?resize=809%2C841&#038;ssl=1" alt="" class="wp-image-1865" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount-Details3.png?w=809&amp;ssl=1 809w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount-Details3.png?resize=289%2C300&amp;ssl=1 289w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount-Details3.png?resize=768%2C798&amp;ssl=1 768w" sizes="auto, (max-width: 809px) 100vw, 809px" /></figure></div>

<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="829" height="771" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount-Details4.png?resize=829%2C771&#038;ssl=1" alt="" class="wp-image-1866" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount-Details4.png?w=829&amp;ssl=1 829w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount-Details4.png?resize=300%2C279&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-CreateServiceAccount-Details4.png?resize=768%2C714&amp;ssl=1 768w" sizes="auto, (max-width: 829px) 100vw, 829px" /></figure></div>


<h2 class="wp-block-heading">產生授權金鑰</h2>



<p>建立完成之後，從列表中點擊剛剛建立的服務帳戶，進入之後點擊在上方的「金鑰」分頁。</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="94" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-ServiceAccountList-1024x94.png?resize=1024%2C94&#038;ssl=1" alt="" class="wp-image-1867" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-ServiceAccountList.png?resize=1024%2C94&amp;ssl=1 1024w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-ServiceAccountList.png?resize=300%2C28&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-ServiceAccountList.png?resize=768%2C71&amp;ssl=1 768w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-ServiceAccountList.png?resize=1536%2C141&amp;ssl=1 1536w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-ServiceAccountList.png?w=1912&amp;ssl=1 1912w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure></div>

<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="878" height="901" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-ServiceAccount-ClickKeysTab.png?resize=878%2C901&#038;ssl=1" alt="" class="wp-image-1868" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-ServiceAccount-ClickKeysTab.png?w=878&amp;ssl=1 878w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-ServiceAccount-ClickKeysTab.png?resize=292%2C300&amp;ssl=1 292w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-ServiceAccount-ClickKeysTab.png?resize=768%2C788&amp;ssl=1 768w" sizes="auto, (max-width: 878px) 100vw, 878px" /></figure></div>


<p>進入金鑰頁面之後，點擊「新增金鑰」後選擇「建立新的金鑰」，接著確認金鑰類型是JSON，按下「建立」按鈕之後，就會讓你下載json格式的金鑰檔案，務必要保存好。</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="709" height="674" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-ServiceAccount-CreateNewPrivateKey.png?resize=709%2C674&#038;ssl=1" alt="" class="wp-image-1869" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-ServiceAccount-CreateNewPrivateKey.png?w=709&amp;ssl=1 709w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-ServiceAccount-CreateNewPrivateKey.png?resize=300%2C285&amp;ssl=1 300w" sizes="auto, (max-width: 709px) 100vw, 709px" /></figure></div>

<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="752" height="477" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-ServiceAccount-CreateNewPrivateKey-JSONKey.png?resize=752%2C477&#038;ssl=1" alt="" class="wp-image-1870" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-ServiceAccount-CreateNewPrivateKey-JSONKey.png?w=752&amp;ssl=1 752w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2022/09/IAM-ServiceAccount-CreateNewPrivateKey-JSONKey.png?resize=300%2C190&amp;ssl=1 300w" sizes="auto, (max-width: 752px) 100vw, 752px" /></figure></div>


<h2 class="wp-block-heading">Docker Login</h2>



<p>使用上面產生的金鑰就可以透過docker login指令登入Registry囉！登入的使用者名稱固定為「_json_key」，密碼的部份就是整個json檔案的內容，下面提供一個相對安全的指令登入方式：</p>



<pre class="wp-block-prismatic-blocks"><code class="language-bash">cat {Your_Json_File} | docker login -u _json_key --password-stdin https://asia-east1-docker.pkg.dev</code></pre>



<p>把上面的{Your_Json_File}的部份取代成下載的json檔名，如果剛剛建立存放區不是選台灣的機房的話，最後面的網址再根據後台顯示的內容修改即可。</p>



<p>登入之後就可以試一下可不可以push docker image，可以從docker hub上面pull一個下來，然後透過docker tag重新更改image repository名稱和tag，就可以push看看囉！</p>
<p>這篇文章 <a href="https://tech.uccu.website/2022ironman-day4-create-registry-and-service-account.html">【2022鐵人賽】Docker Image存放位置選擇：Google Artifact Registry</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tech.uccu.website/2022ironman-day4-create-registry-and-service-account.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1855</post-id>	</item>
		<item>
		<title>【2021鐵人賽】建立自管的Azure DevOps Agent(Linux Container agent)</title>
		<link>https://tech.uccu.website/2021ironman-day22-build-linux-container-image-for-azure-devops-agent.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=2021ironman-day22-build-linux-container-image-for-azure-devops-agent</link>
					<comments>https://tech.uccu.website/2021ironman-day22-build-linux-container-image-for-azure-devops-agent.html#respond</comments>
		
		<dc:creator><![CDATA[鳴人]]></dc:creator>
		<pubDate>Mon, 04 Oct 2021 15:40:42 +0000</pubDate>
				<category><![CDATA[2021鐵人賽]]></category>
		<category><![CDATA[Azure DevOps]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[iThome鐵人賽]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[資訊科技]]></category>
		<category><![CDATA[2021ironman]]></category>
		<category><![CDATA[azure devops]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[docker]]></category>
		<guid isPermaLink="false">https://tech.uccu.website/?p=1292</guid>

					<description><![CDATA[<p>前一篇文章建立了Azure DevOps Agent的Windows Container Image，已經知道了基本的概念就是：1.選擇基底映像檔、2.安裝所需要的額外套件與程式、3.將Azure DevOps Agent的開始程式start.ps/start.sh複製進去。</p>
<p>這篇文章 <a href="https://tech.uccu.website/2021ironman-day22-build-linux-container-image-for-azure-devops-agent.html">【2021鐵人賽】建立自管的Azure DevOps Agent(Linux Container agent)</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p><a href="https://tech.uccu.website/2021ironman-day21-build-windows-container-image-for-azure-devops-agent.html" target="_blank" rel="noreferrer noopener">前一篇文章</a>建立了Azure DevOps Agent的Windows Container Image，已經知道了基本的概念就是：</p>



<ol class="wp-block-list"><li>選擇基底映像檔</li><li>安裝所需要的額外套件與程式</li><li>將Azure DevOps Agent的開始程式start.ps/start.sh複製進去</li></ol>



<p>既然已經知道了上面的概念，前一篇也有做過一次，那麼這篇就少浪費一些篇幅，直接把Code端上來看吧！</p>



<p>start.sh的內容如下，記得要改成Unix格式的結束符號(LF)喔！(或是透過dos2unix轉換)</p>



<pre class="wp-block-prismatic-blocks"><code class="language-bash">#!/bin/bash
set -e

if [ -z &quot;$AZP_URL&quot; ]; then
  echo 1&gt;&amp;2 &quot;error: missing AZP_URL environment variable&quot;
  exit 1
fi

if [ -z &quot;$AZP_TOKEN_FILE&quot; ]; then
  if [ -z &quot;$AZP_TOKEN&quot; ]; then
    echo 1&gt;&amp;2 &quot;error: missing AZP_TOKEN environment variable&quot;
    exit 1
  fi

  AZP_TOKEN_FILE=/azp/.token
  echo -n $AZP_TOKEN &gt; &quot;$AZP_TOKEN_FILE&quot;
fi

unset AZP_TOKEN

if [ -n &quot;$AZP_WORK&quot; ]; then
  mkdir -p &quot;$AZP_WORK&quot;
fi

export AGENT_ALLOW_RUNASROOT=&quot;1&quot;

cleanup() {
  if [ -e config.sh ]; then
    print_header &quot;Cleanup. Removing Azure Pipelines agent...&quot;

    # If the agent has some running jobs, the configuration removal process will fail.
    # So, give it some time to finish the job.
    while true; do
      ./config.sh remove --unattended --auth PAT --token $(cat &quot;$AZP_TOKEN_FILE&quot;) &amp;&amp; break

      echo &quot;Retrying in 30 seconds...&quot;
      sleep 30
    done
  fi
}

print_header() {
  lightcyan=&#039;\033[1;36m&#039;
  nocolor=&#039;\033[0m&#039;
  echo -e &quot;${lightcyan}$1${nocolor}&quot;
}

# Let the agent ignore the token env variables
export VSO_AGENT_IGNORE=AZP_TOKEN,AZP_TOKEN_FILE

source ./env.sh

print_header &quot;1. Configuring Azure Pipelines agent...&quot;

./config.sh --unattended \
  --agent &quot;${AZP_AGENT_NAME:-$(hostname)}&quot; \
  --url &quot;$AZP_URL&quot; \
  --auth PAT \
  --token $(cat &quot;$AZP_TOKEN_FILE&quot;) \
  --pool &quot;${AZP_POOL:-Default}&quot; \
  --work &quot;${AZP_WORK:-_work}&quot; \
  --replace \
  --acceptTeeEula &amp; wait $!

print_header &quot;2. Running Azure Pipelines agent...&quot;

trap &#039;cleanup; exit 0&#039; EXIT
trap &#039;cleanup; exit 130&#039; INT
trap &#039;cleanup; exit 143&#039; TERM

# To be aware of TERM and INT signals call run.sh
# Running it with the --once flag at the end will shut down the agent after the build is executed
./run.sh &quot;$@&quot;</code></pre>



<p>官方的Dockerfile內容如下：</p>



<pre class="wp-block-prismatic-blocks"><code class="language-dockerfile">FROM ubuntu:18.04

# To make it easier for build and release pipelines to run apt-get,
# configure apt to not require confirmation (assume the -y argument by default)
ENV DEBIAN_FRONTEND=noninteractive
RUN echo &quot;APT::Get::Assume-Yes \&quot;true\&quot;;&quot; &gt; /etc/apt/apt.conf.d/90assumeyes

RUN apt-get update &amp;&amp; apt-get install -y --no-install-recommends \
    ca-certificates \
    curl \
    jq \
    git \
    iputils-ping \
    libcurl4 \
    libicu60 \
    libunwind8 \
    netcat \
    libssl1.0 \
  &amp;&amp; rm -rf /var/lib/apt/lists/*

RUN curl -LsS https://aka.ms/InstallAzureCLIDeb | bash \
  &amp;&amp; rm -rf /var/lib/apt/lists/*

ARG TARGETARCH=amd64
ARG AGENT_VERSION=2.185.1

WORKDIR /azp
RUN if [ &quot;$TARGETARCH&quot; = &quot;amd64&quot; ]; then \
      AZP_AGENTPACKAGE_URL=https://vstsagentpackage.azureedge.net/agent/${AGENT_VERSION}/vsts-agent-linux-x64-${AGENT_VERSION}.tar.gz; \
    else \
      AZP_AGENTPACKAGE_URL=https://vstsagentpackage.azureedge.net/agent/${AGENT_VERSION}/vsts-agent-linux-${TARGETARCH}-${AGENT_VERSION}.tar.gz; \
    fi; \
    curl -LsS &quot;$AZP_AGENTPACKAGE_URL&quot; | tar -xz

COPY ./start.sh .
RUN chmod +x start.sh

ENTRYPOINT [ &quot;./start.sh&quot; ]</code></pre>



<p>同樣的，上面的Dockerfile並不包含dotnet sdk，所以仍然需要自行安裝進去。</p>



<p>也和上一篇提到的一樣，我們可以透過在Docker hub上找到官方的dotnet sdk映像檔，找到我們要的OS版本，然後取代Dockerfile第一行的FROM後面使用的image repository：</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="439" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/718841de-dotnetsdkimage-linuxamd64tags-1024x439.png?resize=1024%2C439&#038;ssl=1" alt="" class="wp-image-1298" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/718841de-dotnetsdkimage-linuxamd64tags.png?resize=1024%2C439&amp;ssl=1 1024w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/718841de-dotnetsdkimage-linuxamd64tags.png?resize=300%2C129&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/718841de-dotnetsdkimage-linuxamd64tags.png?resize=768%2C329&amp;ssl=1 768w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/718841de-dotnetsdkimage-linuxamd64tags.png?resize=1536%2C659&amp;ssl=1 1536w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/718841de-dotnetsdkimage-linuxamd64tags.png?w=1665&amp;ssl=1 1665w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>上圖的表格來自於微軟在<a href="https://hub.docker.com/_/microsoft-dotnet-sdk/" target="_blank" rel="noreferrer noopener">Docker Hub上的dotnet sdk頁面</a>，dotnet sdk的image repository是mcr.microsoft.com/dotnet/sdk:5.0，我們只需要將冒號後面的5.0改成需要的就行了，以上面紅框內容為例，就把5.0改為5.0-focal，這樣就是以Ubuntu 20.04為版本安裝了dotnet sdk 5.0製作的Docker image，再加上安裝PowerShell、Docker的話，最終的Dockerfile內容如下：</p>



<pre class="wp-block-prismatic-blocks"><code class="language-dockerfile">FROM mcr.microsoft.com/dotnet/sdk:5.0-focal

# To make it easier for build and release pipelines to run apt-get,
# configure apt to not require confirmation (assume the -y argument by default)
ENV DEBIAN_FRONTEND=noninteractive
RUN echo &quot;APT::Get::Assume-Yes \&quot;true\&quot;;&quot; &gt; /etc/apt/apt.conf.d/90assumeyes

#在這底下加入要額外裝在Docker Image內的程式

RUN apt-get update &amp;&amp; apt-get install -y --no-install-recommends \
    ca-certificates \
    curl \
    jq \
    git \
    iputils-ping \
    libcurl4 \
    libicu66 \
    libunwind8 \
    netcat \
    libssl1.0 \
	wget \
  &amp;&amp; rm -rf /var/lib/apt/lists/*

# 新增 Microsoft 存放庫金鑰和摘要
RUN wget https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb &amp;&amp; \
         dpkg -i packages-microsoft-prod.deb

# 安裝PowerShell
# Enable the &quot;universe&quot; repositories &amp; Install PowerShell
RUN apt-get update &amp;&amp; apt-get install -y powershell

# 安裝Docker
RUN apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add
RUN add-apt-repository \
   &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable&quot;
RUN apt-get update &amp;&amp; apt-get install docker-ce docker-ce-cli containerd.io

#===========================================

RUN curl -LsS https://aka.ms/InstallAzureCLIDeb | bash \
  &amp;&amp; rm -rf /var/lib/apt/lists/*

ARG TARGETARCH=amd64
ARG AGENT_VERSION=2.185.1

WORKDIR /azp
RUN if [ &quot;$TARGETARCH&quot; = &quot;amd64&quot; ]; then \
      AZP_AGENTPACKAGE_URL=https://vstsagentpackage.azureedge.net/agent/${AGENT_VERSION}/vsts-agent-linux-x64-${AGENT_VERSION}.tar.gz; \
    else \
      AZP_AGENTPACKAGE_URL=https://vstsagentpackage.azureedge.net/agent/${AGENT_VERSION}/vsts-agent-linux-${TARGETARCH}-${AGENT_VERSION}.tar.gz; \
    fi; \
    curl -LsS &quot;$AZP_AGENTPACKAGE_URL&quot; | tar -xz

COPY ./start.sh .
RUN chmod +x start.sh

ENTRYPOINT [ &quot;./start.sh&quot; ]</code></pre>



<p>上面的Dockerfile內容中要特別提一下在官方的範例中安裝的libicu60 package只適用在Ubuntu 18.04(<a href="https://askubuntu.com/questions/1225781/unable-to-locate-package-libicu60" target="_blank" rel="noreferrer noopener">參考這篇</a>)，因為我是選Ubuntu 20.04，所以改成libicu66(<a href="https://github.com/dotnet/core/issues/4360#issuecomment-618638299" target="_blank" rel="noreferrer noopener">參考這篇</a>)。</p>



<p>準備好Dockerfile和start.sh這兩個檔案之後，執行下面的指令建立映像檔：</p>



<pre class="wp-block-prismatic-blocks"><code class="language-docker">docker build -t devopsagent .</code></pre>



<p>完成Build Image的動作之後，透過下面的指令建立一個名為azure-agent的Container：</p>



<pre class="wp-block-prismatic-blocks"><code class="language-docker">docker run --name azure-agent -d -v /var/run/docker.sock:/var/run/docker.sock -e AZP_URL=[AzureDevOpsURL] -e AZP_TOKEN=[AzureDevOpsPAT] -e AZP_AGENT_NAME=[AzureDevOpsAgentName] IMAGE:TAG</code></pre>



<p>docker run的指令和<a href="https://tech.uccu.website/2021ironman-day21-build-windows-container-image-for-azure-devops-agent.html" target="_blank" rel="noreferrer noopener">前一篇</a>的大同小異，主要差別在於如何繫結host的docker給container使用。</p>



<p>同樣的環境變數表再貼一次：</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1025" height="408" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/4582d3e6-devopsagent-containerenvironmentvariables.png?resize=1025%2C408&#038;ssl=1" alt="" class="wp-image-1265" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/4582d3e6-devopsagent-containerenvironmentvariables.png?w=1025&amp;ssl=1 1025w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/4582d3e6-devopsagent-containerenvironmentvariables.png?resize=300%2C119&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/4582d3e6-devopsagent-containerenvironmentvariables.png?resize=768%2C306&amp;ssl=1 768w" sizes="auto, (max-width: 1025px) 100vw, 1025px" /><figcaption>Azure DevOps Agent所使用的環境變數</figcaption></figure></div>



<p>同樣貼一下執行的結果：</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1006" height="669" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/b80fcbf2-devopsagent-runagentinlinuxcontainer.png?resize=1006%2C669&#038;ssl=1" alt="" class="wp-image-1301" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/b80fcbf2-devopsagent-runagentinlinuxcontainer.png?w=1006&amp;ssl=1 1006w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/b80fcbf2-devopsagent-runagentinlinuxcontainer.png?resize=300%2C200&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/b80fcbf2-devopsagent-runagentinlinuxcontainer.png?resize=768%2C511&amp;ssl=1 768w" sizes="auto, (max-width: 1006px) 100vw, 1006px" /></figure></div>



<p>還有Agent Pools裡的列表和Agent的Capabilities：</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="793" height="344" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/4e5fe0f5-agentpools-default-linuxcontaineragent.png?resize=793%2C344&#038;ssl=1" alt="" class="wp-image-1304" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/4e5fe0f5-agentpools-default-linuxcontaineragent.png?w=793&amp;ssl=1 793w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/4e5fe0f5-agentpools-default-linuxcontaineragent.png?resize=300%2C130&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/4e5fe0f5-agentpools-default-linuxcontaineragent.png?resize=768%2C333&amp;ssl=1 768w" sizes="auto, (max-width: 793px) 100vw, 793px" /></figure></div>



<div class="wp-block-image"><figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="784" height="927" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/7b7c07bc-agentpools-default-linuxcontaineragent-capabilities.png?resize=784%2C927&#038;ssl=1" alt="" class="wp-image-1307" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/7b7c07bc-agentpools-default-linuxcontaineragent-capabilities.png?w=784&amp;ssl=1 784w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/7b7c07bc-agentpools-default-linuxcontaineragent-capabilities.png?resize=254%2C300&amp;ssl=1 254w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/7b7c07bc-agentpools-default-linuxcontaineragent-capabilities.png?resize=768%2C908&amp;ssl=1 768w" sizes="auto, (max-width: 784px) 100vw, 784px" /></figure></div>



<p>其它和前一篇的內容沒什麼太大的差別，至於Linux VM的部份意思差不多，所以就不再另外弄一篇安裝在Linux VM裡的文章了</p>
<p>這篇文章 <a href="https://tech.uccu.website/2021ironman-day22-build-linux-container-image-for-azure-devops-agent.html">【2021鐵人賽】建立自管的Azure DevOps Agent(Linux Container agent)</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tech.uccu.website/2021ironman-day22-build-linux-container-image-for-azure-devops-agent.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1292</post-id>	</item>
		<item>
		<title>【2021鐵人賽】建立自管的Azure DevOps Agent(Windows Container agent)</title>
		<link>https://tech.uccu.website/2021ironman-day21-build-windows-container-image-for-azure-devops-agent.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=2021ironman-day21-build-windows-container-image-for-azure-devops-agent</link>
					<comments>https://tech.uccu.website/2021ironman-day21-build-windows-container-image-for-azure-devops-agent.html#respond</comments>
		
		<dc:creator><![CDATA[鳴人]]></dc:creator>
		<pubDate>Sun, 03 Oct 2021 11:58:42 +0000</pubDate>
				<category><![CDATA[2021鐵人賽]]></category>
		<category><![CDATA[Azure DevOps]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[iThome鐵人賽]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[資訊科技]]></category>
		<category><![CDATA[2021ironman]]></category>
		<category><![CDATA[azure devops]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[docker]]></category>
		<guid isPermaLink="false">https://tech.uccu.website/?p=1235</guid>

					<description><![CDATA[<p>這篇延續前一篇在Windows VM中安裝Azure DevOps Agent的內容，講解了如何讓Agent在Windows Container中執行，從基底映像檔的差異到挑選適合的映像檔在Dockerfile中使用，也示範如何將Host中Docker提供給Container使用。</p>
<p>這篇文章 <a href="https://tech.uccu.website/2021ironman-day21-build-windows-container-image-for-azure-devops-agent.html">【2021鐵人賽】建立自管的Azure DevOps Agent(Windows Container agent)</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p><a href="https://tech.uccu.website/2021ironman-day20-setup-azure-devops-agent-on-windows-vm.html" target="_blank" rel="noreferrer noopener">前一篇</a>提到了在Windows VM中安裝Azure DevOps Agent，步驟非常的簡單，不過Azure DevOps Agent也可以在Container內執行，這一篇就來看看如何在Windows Container內執行Azure DevOps Agent。(如果需要Windows Server安裝Docker的方式請看<a href="https://tech.uccu.website/install-docker-on-windows-server.html" target="_blank" rel="noreferrer noopener">這篇文章</a>)</p>



<p>首先，在Windows Container的部份有些基本的知識需要了解，那就是Windows Container區分不同的基底映像類型(Base Image)，分為Windows、Windows Server、Windows Server Core、Nano Server四種，越前面的映像檔大小所佔的容量越大，包含的功能越多，越後面的則越小，所包含的功能也越少。(參考官方文件：<a href="https://docs.microsoft.com/zh-tw/virtualization/windowscontainers/manage-containers/container-base-images" target="_blank" rel="noreferrer noopener">中文</a>/<a href="https://docs.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/container-base-images" target="_blank" rel="noreferrer noopener">英文</a>)</p>



<p>另外就是Windows Container的隔離模式(Isolation  Modes)，分為Process和Hyper-V隔離這兩種，在Windows Server上執行的Windows Container預設以Process隔離方式執行，在Windows 10執行的Windows Container則預設以Hyper-V隔離方式執行。(參考官方文件：<a href="https://docs.microsoft.com/zh-tw/virtualization/windowscontainers/manage-containers/hyperv-container" target="_blank" rel="noreferrer noopener">中文</a>/<a href="https://docs.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/hyperv-container" target="_blank" rel="noreferrer noopener">英文</a>)</p>



<p>在上面這兩個不同的差異之下，選擇建立Windows Container Image就會有很多的選擇，這牽涉到執行Container的主機(Host)所使用的是哪一個版本的OS，不同版本之間的相容性會有所差異，例如：Windows Server 2019與Windows Server 20H2這兩個版本的OS相容性：</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="574" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/e7ee6766-oscompatibility-winserver2019.png?resize=1024%2C574&#038;ssl=1" alt="" class="wp-image-1238" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/e7ee6766-oscompatibility-winserver2019.png?w=1024&amp;ssl=1 1024w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/e7ee6766-oscompatibility-winserver2019.png?resize=300%2C168&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/e7ee6766-oscompatibility-winserver2019.png?resize=768%2C431&amp;ssl=1 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption>Windows Server 2019的OS相容性</figcaption></figure></div>



<div class="wp-block-image"><figure class="aligncenter size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="573" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/d95ace76-oscompatibility-winserver20h2-1024x573.png?resize=1024%2C573&#038;ssl=1" alt="" class="wp-image-1241" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/d95ace76-oscompatibility-winserver20h2.png?resize=1024%2C573&amp;ssl=1 1024w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/d95ace76-oscompatibility-winserver20h2.png?resize=300%2C168&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/d95ace76-oscompatibility-winserver20h2.png?resize=768%2C430&amp;ssl=1 768w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/d95ace76-oscompatibility-winserver20h2.png?w=1031&amp;ssl=1 1031w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption>Windows Server 20H2的OS相容性</figcaption></figure></div>



<p>從上面兩張圖可以看到Windows Server 2019的Host可以支援以Windows Server 2019為基底的Container Image以Process隔離模式執行，若是以Hyper-V隔離模式執行，則是可以支援Windows Server 2019以下的版本。同樣的，Windows Server 20H2的Host可以支援同為Windows Server 20H2的Image以Process隔離模式執行，Hyper-V的隔離模式則是與Host相同版本以下的都能夠支援。</p>



<p>換成是Windows 10作為Host的話，支援性又有不同的差異：</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1015" height="566" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/a65d2b83-oscompatibility-win10_1909.png?resize=1015%2C566&#038;ssl=1" alt="" class="wp-image-1244" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/a65d2b83-oscompatibility-win10_1909.png?w=1015&amp;ssl=1 1015w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/a65d2b83-oscompatibility-win10_1909.png?resize=300%2C167&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/a65d2b83-oscompatibility-win10_1909.png?resize=768%2C428&amp;ssl=1 768w" sizes="auto, (max-width: 1015px) 100vw, 1015px" /><figcaption>Windows 10 1909版本的OS相容性</figcaption></figure></div>



<div class="wp-block-image"><figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1016" height="574" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/06a1c852-oscompatibility-win10_2004.png?resize=1016%2C574&#038;ssl=1" alt="" class="wp-image-1247" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/06a1c852-oscompatibility-win10_2004.png?w=1016&amp;ssl=1 1016w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/06a1c852-oscompatibility-win10_2004.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/06a1c852-oscompatibility-win10_2004.png?resize=768%2C434&amp;ssl=1 768w" sizes="auto, (max-width: 1016px) 100vw, 1016px" /><figcaption>Windows 10 2004版本的OS相容性</figcaption></figure></div>



<div class="wp-block-image"><figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1017" height="570" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/530b0e7f-oscompatibility-win10_20h2.png?resize=1017%2C570&#038;ssl=1" alt="" class="wp-image-1250" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/530b0e7f-oscompatibility-win10_20h2.png?w=1017&amp;ssl=1 1017w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/530b0e7f-oscompatibility-win10_20h2.png?resize=300%2C168&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/530b0e7f-oscompatibility-win10_20h2.png?resize=768%2C430&amp;ssl=1 768w" sizes="auto, (max-width: 1017px) 100vw, 1017px" /><figcaption>Windows 10 20H2版本的OS相容性</figcaption></figure></div>



<p>Windows 10的部份可以從上面三張圖看到一點點差異，就是Windows 10的1909版本並不能支援Windows Server任何版本為基底映像檔以Process隔離模式執行，Windows 10的2004版本之後才開始支援與Windows Server相對應的版本映像檔以Process隔離模式執行，但是以Hyper-V隔離模式執行的話則是支援相同版本以下的OS映像檔。</p>



<p>有點複雜，對吧？</p>



<p>簡單來說，要能夠支援以Process隔離模式執行，必須是Container Base Image和Host為相同版本的OS，若是以Hyper-V隔離模式執行，則必須挑選Host OS版本以下製成的Container Image，也就是說Windows Server 2016的選擇性非常少，只能使用同為Windows Server 2016為基底製作的映像檔，越新的OS版本則支援以Hyper-V隔離模式執行的Container Image選擇越多。</p>



<p>更多Windows Container版本相容性的部份可以參考官方文件的說明(<a href="https://docs.microsoft.com/zh-tw/virtualization/windowscontainers/deploy-containers/version-compatibility" target="_blank" rel="noreferrer noopener">中文</a>/<a href="https://docs.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/version-compatibility" target="_blank" rel="noreferrer noopener">英文</a>)。</p>



<p>回到Azure DevOps Agent，上面說了那麼多，主要是必須考量Agent要執行的任務需要多大的支援與資源，若是只需要能夠以dotnet sdk建置專案，那麼選擇任何一個OS版本的Image應該都可以，只要能夠在Image裡面安裝dotnet sdk即可。</p>



<p>以dotnet sdk這個例子來說，在官方文件「<a href="https://docs.microsoft.com/zh-tw/azure/devops/pipelines/agents/docker?view=azure-devops" target="_blank" rel="noreferrer noopener">在 Docker 中執行自我裝載的代理程式</a>」所提到的Dockerfile範例是使用Windows Server Core 2019的基底映像檔，裡面並沒有安裝dotnet sdk：</p>



<pre class="wp-block-prismatic-blocks"><code class="language-dockerfile">FROM mcr.microsoft.com/windows/servercore:ltsc2019

WORKDIR /azp

COPY start.ps1 .

CMD powershell .\start.ps1</code></pre>



<p>所以要讓Agent可以執行dotnet build的任務，可以選擇使用上面的範例，在Dockerfile中再另外加入安裝dotnet sdk的內容，或是從<a href="https://hub.docker.com/" target="_blank" rel="noreferrer noopener">Docker hub</a>上另外找<a href="https://hub.docker.com/_/microsoft-dotnet-sdk/" target="_blank" rel="noreferrer noopener">微軟官方製作的dotnet sdk映像檔</a>，找到對應的Container Repository與Image tag，例如：「mcr.microsoft.com/dotnet/sdk:5.0-windowsservercore-ltsc2019」這個就是同樣以Windows Server Core 2019為基底的Image，並且安裝了dotnet 5.0 sdk。</p>



<p>從官方文件「<a href="https://docs.microsoft.com/zh-tw/azure/devops/pipelines/agents/docker?view=azure-devops" target="_blank" rel="noreferrer noopener">在 Docker 中執行自我裝載的代理程式</a>」 中的範例可以看得出來，在Container中執行Azure DevOps Agent的關鍵在於下面這段PowerShell內容，將它複製之後以start.ps1儲存在與Dockerfile相同的位置即可：</p>



<pre class="wp-block-prismatic-blocks"><code class="language-powershell">if (-not (Test-Path Env:AZP_URL)) {
  Write-Error &quot;error: missing AZP_URL environment variable&quot;
  exit 1
}

if (-not (Test-Path Env:AZP_TOKEN_FILE)) {
  if (-not (Test-Path Env:AZP_TOKEN)) {
    Write-Error &quot;error: missing AZP_TOKEN environment variable&quot;
    exit 1
  }

  $Env:AZP_TOKEN_FILE = &quot;\azp\.token&quot;
  $Env:AZP_TOKEN | Out-File -FilePath $Env:AZP_TOKEN_FILE
}

Remove-Item Env:AZP_TOKEN

if ((Test-Path Env:AZP_WORK) -and -not (Test-Path $Env:AZP_WORK)) {
  New-Item $Env:AZP_WORK -ItemType directory | Out-Null
}

New-Item &quot;\azp\agent&quot; -ItemType directory | Out-Null

# Let the agent ignore the token env variables
$Env:VSO_AGENT_IGNORE = &quot;AZP_TOKEN,AZP_TOKEN_FILE&quot;

Set-Location agent

Write-Host &quot;1. Determining matching Azure Pipelines agent...&quot; -ForegroundColor Cyan

$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(&quot;:$(Get-Content ${Env:AZP_TOKEN_FILE})&quot;))
$package = Invoke-RestMethod -Headers @{Authorization=(&quot;Basic $base64AuthInfo&quot;)} &quot;$(${Env:AZP_URL})/_apis/distributedtask/packages/agent?platform=win-x64&amp;`$top=1&quot;
$packageUrl = $package[0].Value.downloadUrl

Write-Host $packageUrl

Write-Host &quot;2. Downloading and installing Azure Pipelines agent...&quot; -ForegroundColor Cyan

$wc = New-Object System.Net.WebClient
$wc.DownloadFile($packageUrl, &quot;$(Get-Location)\agent.zip&quot;)

Expand-Archive -Path &quot;agent.zip&quot; -DestinationPath &quot;\azp\agent&quot;

try
{
  Write-Host &quot;3. Configuring Azure Pipelines agent...&quot; -ForegroundColor Cyan

  .\config.cmd --unattended `
    --agent &quot;$(if (Test-Path Env:AZP_AGENT_NAME) { ${Env:AZP_AGENT_NAME} } else { ${Env:computername} })&quot; `
    --url &quot;$(${Env:AZP_URL})&quot; `
    --auth PAT `
    --token &quot;$(Get-Content ${Env:AZP_TOKEN_FILE})&quot; `
    --pool &quot;$(if (Test-Path Env:AZP_POOL) { ${Env:AZP_POOL} } else { &#039;Default&#039; })&quot; `
    --work &quot;$(if (Test-Path Env:AZP_WORK) { ${Env:AZP_WORK} } else { &#039;_work&#039; })&quot; `
    --replace

  Write-Host &quot;4. Running Azure Pipelines agent...&quot; -ForegroundColor Cyan

  .\run.cmd
}
finally
{
  Write-Host &quot;Cleanup. Removing Azure Pipelines agent...&quot; -ForegroundColor Cyan

  .\config.cmd remove --unattended `
    --auth PAT `
    --token &quot;$(Get-Content ${Env:AZP_TOKEN_FILE})&quot;
}</code></pre>



<p>接著就是準備我們要用的Dockerfile，這邊我以dotnet 5.0 sdk的Windows Server Core 2022映像檔為基底，它裡面包含了PowerShell與dotnet 5.0 sdk，並且我還要額外在Image裡面安裝Docker。若是需要dotnet core 3.1與其它OS基底映像檔的部份可以到<a href="https://hub.docker.com/_/microsoft-dotnet-sdk/" target="_blank" rel="noreferrer noopener">Docker hub的頁面</a>上去尋找相關的Tag：</p>



<pre class="wp-block-prismatic-blocks"><code class="language-dockerfile">FROM mcr.microsoft.com/dotnet/sdk:5.0-windowsservercore-ltsc2022

#在這底下加入要額外裝在Docker Image內的程式

SHELL [&quot;powershell&quot;, &quot;-Command&quot;, &quot;$ErrorActionPreference = &#039;Stop&#039;; $ProgressPreference = &#039;SilentlyContinue&#039;;&quot;]

RUN Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
RUN Install-Module -Name DockerMsftProvider -Repository PSGallery -Force -verbose
RUN Install-Package -Name docker -ProviderName DockerMsftProvider -Force -verbose -ErrorAction SilentlyContinue; exit 0

#===========================================

WORKDIR /azp

COPY start.ps1 .

CMD powershell .\start.ps1</code></pre>



<p>準備好Dockerfile和start.ps1這兩個檔案之後，執行下面的指令建立映像檔：</p>



<pre class="wp-block-prismatic-blocks"><code class="language-docker">docker build -t devopsagent .</code></pre>



<p>完成Build Image的動作之後，透過下面的指令建立一個名為azure-agent的Container：</p>



<pre class="wp-block-prismatic-blocks"><code class="language-docker">docker run --name azure-agent -d -v \.\pipe\docker_engine:\.\pipe\docker_engine -e AZP_URL=[AzureDevOpsURL] -e AZP_TOKEN=[AzureDevOpsPAT] -e AZP_AGENT_NAME=[AzureDevOpsAgentName] IMAGE:TAG</code></pre>



<p>指令中比較特別的地方是透過mount的方式將host的docker轉給container內使用，也就是「-v \.\pipe\docker_engine:\.\pipe\docker_engine」這段，其它的除了IMAGE:TAG是根據上面的docker build所指定的Repository與Tag(預設為latest)之外，環境變數的部份則參考下面這張表：</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1025" height="408" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/4582d3e6-devopsagent-containerenvironmentvariables.png?resize=1025%2C408&#038;ssl=1" alt="" class="wp-image-1265" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/4582d3e6-devopsagent-containerenvironmentvariables.png?w=1025&amp;ssl=1 1025w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/4582d3e6-devopsagent-containerenvironmentvariables.png?resize=300%2C119&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/4582d3e6-devopsagent-containerenvironmentvariables.png?resize=768%2C306&amp;ssl=1 768w" sizes="auto, (max-width: 1025px) 100vw, 1025px" /><figcaption>Azure DevOps Agent所使用的環境變數</figcaption></figure></div>



<p>五個環境變數中的最後兩個AZP_POOL、AZP_WORK是選擇性的，只有前三個是必須的，執行之後的結果如下：</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="755" height="533" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/2af50f9a-devopsagent-runagentinwindowscontainer.png?resize=755%2C533&#038;ssl=1" alt="" class="wp-image-1268" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/2af50f9a-devopsagent-runagentinwindowscontainer.png?w=755&amp;ssl=1 755w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/2af50f9a-devopsagent-runagentinwindowscontainer.png?resize=300%2C212&amp;ssl=1 300w" sizes="auto, (max-width: 755px) 100vw, 755px" /><figcaption>Azure DevOps Agent執行在Windows Container中</figcaption></figure></div>



<p>從Azure DevOps中的Agent Pools列表中可以看到這個Agent：</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="793" height="277" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/6ad9bba5-agentpools-default-wincontaineragent.png?resize=793%2C277&#038;ssl=1" alt="" class="wp-image-1271" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/6ad9bba5-agentpools-default-wincontaineragent.png?w=793&amp;ssl=1 793w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/6ad9bba5-agentpools-default-wincontaineragent.png?resize=300%2C105&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/6ad9bba5-agentpools-default-wincontaineragent.png?resize=768%2C268&amp;ssl=1 768w" sizes="auto, (max-width: 793px) 100vw, 793px" /></figure></div>



<p>進入查看Capabilities的部份也可以看到幾個比較不一樣的資訊，像是ComputerName是Docker產生的隨機名稱，還有docker的資訊：</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="790" height="927" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/df70a783-agentpools-default-wincontaineragent-capabilities.png?resize=790%2C927&#038;ssl=1" alt="" class="wp-image-1274" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/df70a783-agentpools-default-wincontaineragent-capabilities.png?w=790&amp;ssl=1 790w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/df70a783-agentpools-default-wincontaineragent-capabilities.png?resize=256%2C300&amp;ssl=1 256w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/df70a783-agentpools-default-wincontaineragent-capabilities.png?resize=768%2C901&amp;ssl=1 768w" sizes="auto, (max-width: 790px) 100vw, 790px" /><figcaption>Azure DevOps Agent執行在Windows Container中的Capabilities資訊</figcaption></figure></div>



<p>至於為什麼需要將host的docker提供給Container中的docker使用，這部份則是為了讓Container中的docker可以共用host上的docker，從Container中的Agent執行docker build所產生的docker image也會儲存在host的docker images裡面，並且可以代替host執行docker run建立新的Container。不過這麼做也是有安全性的風險的，<a href="https://docs.microsoft.com/zh-tw/azure/devops/pipelines/agents/docker?view=azure-devops#use-docker-within-a-docker-container" target="_blank" rel="noreferrer noopener">官方就有提出警告</a>，是否要這麼做則是可以自行思考決定：</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1017" height="367" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/7582834f-waring-usedockerindocker.png?resize=1017%2C367&#038;ssl=1" alt="" class="wp-image-1277" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/7582834f-waring-usedockerindocker.png?w=1017&amp;ssl=1 1017w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/7582834f-waring-usedockerindocker.png?resize=300%2C108&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/7582834f-waring-usedockerindocker.png?resize=768%2C277&amp;ssl=1 768w" sizes="auto, (max-width: 1017px) 100vw, 1017px" /></figure></div>



<p>會在這篇文章中這麼做的原因，純粹是為了提供Windows環境將docker轉給Container內使用的指令使用方式，因為這部份在微軟的文件頁面上似乎沒有提到(我忘了之前從哪裡看到的)。</p>
<p>這篇文章 <a href="https://tech.uccu.website/2021ironman-day21-build-windows-container-image-for-azure-devops-agent.html">【2021鐵人賽】建立自管的Azure DevOps Agent(Windows Container agent)</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tech.uccu.website/2021ironman-day21-build-windows-container-image-for-azure-devops-agent.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1235</post-id>	</item>
		<item>
		<title>Windows Server安裝Docker與Container功能</title>
		<link>https://tech.uccu.website/install-docker-on-windows-server.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=install-docker-on-windows-server</link>
					<comments>https://tech.uccu.website/install-docker-on-windows-server.html#respond</comments>
		
		<dc:creator><![CDATA[鳴人]]></dc:creator>
		<pubDate>Sat, 02 Oct 2021 18:00:21 +0000</pubDate>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[windows server]]></category>
		<guid isPermaLink="false">https://tech.uccu.website/?p=1211</guid>

					<description><![CDATA[<p>Windows Server要安裝Docker很簡單，只需要透過具有Administrator權限的PowerShell執行三行指令就可以完成安裝Docker的動作，分別是安裝Provider、安裝Docker、重新開機。</p>
<p>這篇文章 <a href="https://tech.uccu.website/install-docker-on-windows-server.html">Windows Server安裝Docker與Container功能</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p>Windows 10 Pro版本以上要安裝Docker不難，只要從docker的網站下載<a href="https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe" target="_blank" rel="noreferrer noopener">Docker Desktop for Windows的安裝檔</a>，一般來說就是一直下一步就行了，如果是Windows 10 Home版想要安裝Docker，因為Docker需要Hyper-V的功能，所以Windows 10 Home版需要透過一些技巧安裝Hyper-V功能之後再安裝Docker，詳細的內容可以參考先前的文章：<a href="https://tech.uccu.website/windows10-home-install-hyperv-docker.html" target="_blank" rel="noreferrer noopener">Windows 10 Home家用版安裝Hyper-V與Docker</a>。</p>



<p>最近在寫<a href="https://tech.uccu.website/2021ironman" target="_blank" rel="noreferrer noopener">iThome鐵人賽的文章</a>，有一篇文章需要在Windows Server上使用Docker與Container的功能，在Azure上建立了一個VM，所以就順便將過程記錄一下。</p>



<p>首先，必須先將Container功能安裝進去(如果這台也要安裝Docker可以跳過這步)，透過ServerManager的AddRolesAndFeatures叫出UI介面之後在Features的部份找到Containers：</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="374" height="167" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/7545dbd7-windowsserver-servermanager-addrolesandfeatures.png?resize=374%2C167&#038;ssl=1" alt="" class="wp-image-1214" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/7545dbd7-windowsserver-servermanager-addrolesandfeatures.png?w=374&amp;ssl=1 374w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/7545dbd7-windowsserver-servermanager-addrolesandfeatures.png?resize=300%2C134&amp;ssl=1 300w" sizes="auto, (max-width: 374px) 100vw, 374px" /></figure></div>



<div class="wp-block-image"><figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="785" height="561" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/d4e98f13-windowsserver-servermanager-addrolesandfeatures-features-containers.png?resize=785%2C561&#038;ssl=1" alt="" class="wp-image-1217" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/d4e98f13-windowsserver-servermanager-addrolesandfeatures-features-containers.png?w=785&amp;ssl=1 785w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/d4e98f13-windowsserver-servermanager-addrolesandfeatures-features-containers.png?resize=300%2C214&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/d4e98f13-windowsserver-servermanager-addrolesandfeatures-features-containers.png?resize=768%2C549&amp;ssl=1 768w" sizes="auto, (max-width: 785px) 100vw, 785px" /></figure></div>



<p>勾選之後安裝完需要重新開機。</p>



<p>重新開機之後開啟具有Administrator權限的PowerShell，輸入下面這行指令：</p>



<pre class="wp-block-prismatic-blocks"><code class="language-powershell">Install-Module -Name DockerMsftProvider -Repository PSGallery -Force</code></pre>



<p>上面這行是安裝稱為&nbsp;<a href="https://github.com/OneGet/MicrosoftDockerProvider">DockerMicrosoftProvider</a>&nbsp;的&nbsp;<a href="https://github.com/oneget/oneget">OneGet 提供者 PowerShell 模組</a>。並且需要NuGet提供者(Provider)，如果沒安裝過的話會一併提示是否安裝(預設為Yes)：</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="859" height="173" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/72bb2e85-windowsserver-powershell-installdockerprovider.png?resize=859%2C173&#038;ssl=1" alt="" class="wp-image-1220" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/72bb2e85-windowsserver-powershell-installdockerprovider.png?w=859&amp;ssl=1 859w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/72bb2e85-windowsserver-powershell-installdockerprovider.png?resize=300%2C60&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/72bb2e85-windowsserver-powershell-installdockerprovider.png?resize=768%2C155&amp;ssl=1 768w" sizes="auto, (max-width: 859px) 100vw, 859px" /></figure></div>



<p>接著輸入下面這行指令，從剛才安裝的DockerMicrosoftProvider中安裝Docker：</p>



<pre class="wp-block-prismatic-blocks"><code class="language-powershell">Install-Package -Name docker -ProviderName DockerMsftProvider</code></pre>



<div class="wp-block-image"><figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="808" height="183" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/8ba0e065-windowsserver-powershell-installdocker.png?resize=808%2C183&#038;ssl=1" alt="" class="wp-image-1223" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/8ba0e065-windowsserver-powershell-installdocker.png?w=808&amp;ssl=1 808w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/8ba0e065-windowsserver-powershell-installdocker.png?resize=300%2C68&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/8ba0e065-windowsserver-powershell-installdocker.png?resize=768%2C174&amp;ssl=1 768w" sizes="auto, (max-width: 808px) 100vw, 808px" /></figure></div>



<p>安裝完成之後需要重新啟動電腦，可以透過UI操作，或是輸入下面的PowerShell指令：</p>



<pre class="wp-block-prismatic-blocks"><code class="language-powershell">Restart-Computer -Force</code></pre>



<p>重開機之後再輸入docker info就可以看到docker相關的資訊了。</p>



<div class="wp-block-image"><figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="627" height="649" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/ff2ef8d6-windowsserver-powershell-dockerinfo.png?resize=627%2C649&#038;ssl=1" alt="" class="wp-image-1226" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/ff2ef8d6-windowsserver-powershell-dockerinfo.png?w=627&amp;ssl=1 627w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2021/10/ff2ef8d6-windowsserver-powershell-dockerinfo.png?resize=290%2C300&amp;ssl=1 290w" sizes="auto, (max-width: 627px) 100vw, 627px" /></figure></div>



<p>參考連結：</p>



<ul class="wp-block-list"><li><a href="https://docs.microsoft.com/zh-tw/virtualization/windowscontainers/quick-start/set-up-environment?tabs=Windows-Server" target="_blank" rel="noreferrer noopener">開始使用：準備適用於容器的 Windows</a></li></ul>
<p>這篇文章 <a href="https://tech.uccu.website/install-docker-on-windows-server.html">Windows Server安裝Docker與Container功能</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tech.uccu.website/install-docker-on-windows-server.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1211</post-id>	</item>
		<item>
		<title>解決.sh檔案在Container內執行時的換行字元(CRLF/LF)問題</title>
		<link>https://tech.uccu.website/convert-shell-file-end-of-line-sequence-at-dockerfile.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=convert-shell-file-end-of-line-sequence-at-dockerfile</link>
					<comments>https://tech.uccu.website/convert-shell-file-end-of-line-sequence-at-dockerfile.html#respond</comments>
		
		<dc:creator><![CDATA[鳴人]]></dc:creator>
		<pubDate>Tue, 24 Nov 2020 09:47:26 +0000</pubDate>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[crlf]]></category>
		<category><![CDATA[dockerfile]]></category>
		<category><![CDATA[dos2unix]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[shell]]></category>
		<guid isPermaLink="false">https://tech.uccu.website/?p=158</guid>

					<description><![CDATA[<p>如果有在使用Docker並且是建置Linux環境的Image，搭配自行撰寫的shell檔案，有時候會因為版本控 ... <a title="解決.sh檔案在Container內執行時的換行字元(CRLF/LF)問題" class="read-more" href="https://tech.uccu.website/convert-shell-file-end-of-line-sequence-at-dockerfile.html" aria-label="Read more about 解決.sh檔案在Container內執行時的換行字元(CRLF/LF)問題">閱讀全文</a></p>
<p>這篇文章 <a href="https://tech.uccu.website/convert-shell-file-end-of-line-sequence-at-dockerfile.html">解決.sh檔案在Container內執行時的換行字元(CRLF/LF)問題</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p>如果有在使用Docker並且是建置Linux環境的Image，搭配自行撰寫的shell檔案，有時候會因為版本控管、編輯器(IDE)等雜七雜八的因素，導致在Dockerfile中複製到Image的時候看起來雖然沒問題，但是在Run Container的時候出現因為換行字元(CRLF/LF)導致的錯誤時，除了可以回頭去調整或修改shell檔案，將換行字元從CRLF轉換成LF之外，還有一個更方便的方法，那就是在shell檔案複製到Docker Image的時候再執行轉換的動作。</p>



<p>起先我在寫Shell檔案的時候真的是碰到了許多次因為CRLF / LF換行字元的問題，早期都是回頭去修改原始檔案之後再重新Build Image，久了之後實在是覺得很麻煩，某天忽然想到直接在Dockerfile中安裝<em><strong>dos2unix</strong></em>這個套件，在shell檔案複製之後再執行它來將shell檔案的換行字元的問題解決，從此之後就沒有再為了Windows / Unix-like不同環境的換行字元問題而煩惱過了，因此特別用這篇文章簡短記錄一下，也許有人也為了這個問題困擇了一段時間呢！</p>



<h2 class="wp-block-heading">Dockerfile內容參考範例</h2>



<pre class="wp-block-prismatic-blocks"><code class="language-docker">RUN apt-get install dos2unix;
COPY my-init.sh .
RUN dos2unix my-init.sh
CMD [&quot;bash&quot;, &quot;my-init.sh&quot;]</code></pre>
<p>這篇文章 <a href="https://tech.uccu.website/convert-shell-file-end-of-line-sequence-at-dockerfile.html">解決.sh檔案在Container內執行時的換行字元(CRLF/LF)問題</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tech.uccu.website/convert-shell-file-end-of-line-sequence-at-dockerfile.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">158</post-id>	</item>
		<item>
		<title>WSL啟動時，自動啟動Docker服務</title>
		<link>https://tech.uccu.website/auto-start-docker-service-when-wsl-start.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=auto-start-docker-service-when-wsl-start</link>
					<comments>https://tech.uccu.website/auto-start-docker-service-when-wsl-start.html#respond</comments>
		
		<dc:creator><![CDATA[鳴人]]></dc:creator>
		<pubDate>Thu, 10 Sep 2020 09:11:37 +0000</pubDate>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[wsl]]></category>
		<guid isPermaLink="false">https://tech.uccu.website/?p=94</guid>

					<description><![CDATA[<p>如果有看前一篇文章，也就是在WSL中安裝原生的Docker環境，可能在電腦重新啟動之後輸入wsl進入Linux ... <a title="WSL啟動時，自動啟動Docker服務" class="read-more" href="https://tech.uccu.website/auto-start-docker-service-when-wsl-start.html" aria-label="Read more about WSL啟動時，自動啟動Docker服務">閱讀全文</a></p>
<p>這篇文章 <a href="https://tech.uccu.website/auto-start-docker-service-when-wsl-start.html">WSL啟動時，自動啟動Docker服務</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p>如果有看前一篇文章，也就是在WSL中安裝原生的Docker環境，可能在電腦重新啟動之後輸入wsl進入Linux子系統時，會發現docker怎麼好像又不能使用了？</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="152" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/5682d84c-image-1024x152.png?resize=1024%2C152&#038;ssl=1" alt="Docker服務未啟動(Server無法連線)" class="wp-image-84" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/5682d84c-image.png?resize=1024%2C152&amp;ssl=1 1024w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/5682d84c-image.png?resize=300%2C45&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/5682d84c-image.png?resize=768%2C114&amp;ssl=1 768w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/5682d84c-image.png?w=1351&amp;ssl=1 1351w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption>重新啟動wsl之後輸入docker的指令無法使用</figcaption></figure>



<p>其實這只是因為電腦重新啟動後，WSL環境也是重新啟動，所有額外安裝的程式與服務都需要手動重新啟動它們，所以這時候只要再輸入<code>sudo service docker start</code>，再執行docker的指令就可以正常工作了。</p>



<p>不過每次WSL重新啟動之後都需要輸入指令讓docker服務啟動，說實在的有一點麻煩，因為通常用到WSL的時候就是要在裡面跑docker container，所以如果在輸入wsl進入Linux子系統環境的時候就能夠自動將服務啟動的話，那不是方便很多嗎？</p>



<p>這一篇文章就是說明該如何在Linux子系統設定自動啟動docker服務，這個概念其實就和Windows作業系統啟動的時候要執行什麼事情所做的設定是相同的概念，所以也不限於使用在WSL的環境喔！</p>



<span id="more-94"></span>



<h3 class="wp-block-heading">自動啟動Docker服務</h3>



<p>首先，進入WSL之後，輸入<code>cd ~</code>切換到使用者的根目錄，接著再輸入<code>sudo nano .bashrc</code>，透過nano編輯.bashrc這個檔案。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="537" height="99" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/1fdf22ea-image.png?resize=537%2C99&#038;ssl=1" alt="" class="wp-image-95" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/1fdf22ea-image.png?w=537&amp;ssl=1 537w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/1fdf22ea-image.png?resize=300%2C55&amp;ssl=1 300w" sizes="auto, (max-width: 537px) 100vw, 537px" /><figcaption>切換到使用者根目錄編輯.bashrc檔案</figcaption></figure></div>



<p>接著會看到裡面有一堆的設定值，直接在最上面第一行將啟動docker的指令<code>sudo service docker start</code>輸入進去，然後按下鍵盤的ctrl+x離開，詢問要不要儲存檔案的時候按y，接著再直接按enter就行了。</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="233" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/b96156ef-image-1024x233.png?resize=1024%2C233&#038;ssl=1" alt="" class="wp-image-96" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/b96156ef-image.png?resize=1024%2C233&amp;ssl=1 1024w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/b96156ef-image.png?resize=300%2C68&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/b96156ef-image.png?resize=768%2C175&amp;ssl=1 768w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/b96156ef-image.png?w=1297&amp;ssl=1 1297w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption>在.bashrc檔案的第一行加入啟動docker服務的指令</figcaption></figure>



<p>到這個動作其實已經可以讓WSL在啟動的時候自動將docker服務啟動起來，不過這樣子只能算是完成了一半，因為在WSL啟動的時候仍然需要一個麻煩的動作：輸入密碼。</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="201" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/e0044eed-image-1024x201.png?resize=1024%2C201&#038;ssl=1" alt="" class="wp-image-97" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/e0044eed-image.png?resize=1024%2C201&amp;ssl=1 1024w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/e0044eed-image.png?resize=300%2C59&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/e0044eed-image.png?resize=768%2C151&amp;ssl=1 768w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/e0044eed-image.png?resize=1536%2C302&amp;ssl=1 1536w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/e0044eed-image.png?w=1675&amp;ssl=1 1675w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption>WSL啟動時因為利用sudo去啟動docker服務，所以需要輸入密碼</figcaption></figure>



<h3 class="wp-block-heading">省略輸入密碼設定</h3>



<p>其實輸入密碼的這個動作也是可以設定成不需要輸入，同樣是透過編輯設定就行。</p>



<p>進入WSL之後輸入<code>sudo visudo</code>，進入設定編輯畫面，這次我們選擇在最下面一行輸入設定值：</p>



<p><code>&lt;em&gt;帳號&lt;/em&gt; ALL=(ALL) NOPASSWD: ALL</code></p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="642" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/1897d48c-image-1024x642.png?resize=1024%2C642&#038;ssl=1" alt="" class="wp-image-98" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/1897d48c-image.png?resize=1024%2C642&amp;ssl=1 1024w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/1897d48c-image.png?resize=300%2C188&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/1897d48c-image.png?resize=768%2C482&amp;ssl=1 768w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/1897d48c-image.png?w=1427&amp;ssl=1 1427w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption>將WSL環境執行的帳號加入sudoer，執行sudo指令的時候不需要輸入密碼</figcaption></figure>



<p>輸入完之後，同樣按下鍵盤的ctrl+x離開，詢問要不要儲存檔案的時候按y，接著再直接按enter離開就行了。</p>



<p>完成之後同樣離開WSL環境，並且將WSL終止(<code>wsl -t &lt;em&gt;WSL子系統Distro名稱&lt;/em&gt;</code>)，重新輸入wsl進入Linux子系統，可以看到下圖與上面的差異，需要輸入密碼的步驟已經省略掉了。</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="128" src="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/49f7c810-image-1024x128.png?resize=1024%2C128&#038;ssl=1" alt="" class="wp-image-99" srcset="https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/49f7c810-image.png?resize=1024%2C128&amp;ssl=1 1024w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/49f7c810-image.png?resize=300%2C37&amp;ssl=1 300w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/49f7c810-image.png?resize=768%2C96&amp;ssl=1 768w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/49f7c810-image.png?resize=1536%2C192&amp;ssl=1 1536w, https://i0.wp.com/storage.googleapis.com/stateless-tech-uccu-website/2020/08/49f7c810-image.png?w=1673&amp;ssl=1 1673w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption>進入WSL環境並且啟動docker服務已經不需要再輸入密碼了</figcaption></figure>



<h3 class="wp-block-heading">參考來源</h3>



<ul class="wp-block-list"><li><a aria-label="undefined (opens in a new tab)" href="https://superuser.com/questions/1343558/hot-to-make-wsl-run-services-at-startup" target="_blank" rel="noreferrer noopener nofollow">How to make WSL run services at startup</a></li></ul>
<p>這篇文章 <a href="https://tech.uccu.website/auto-start-docker-service-when-wsl-start.html">WSL啟動時，自動啟動Docker服務</a> 最早出現於 <a href="https://tech.uccu.website">泰克哪裡去</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tech.uccu.website/auto-start-docker-service-when-wsl-start.html/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">94</post-id>	</item>
	</channel>
</rss>
