--- tags: Docker, Docker Compose, DevOps,Visual Studio --- <!-- Dark Theme for HackMD --> <!-- markdownlint-disable MD041 --> {%hackmd BJrTq20hE %} # 使用 ***SSL*** 與 ***Domain Name*** 在Windows本機開發後端應用 撰寫者 : Cymon Dez 撰寫時間 : 2020-08-20 修改時間 : * 2020-09-02 追加相似開源專案參考資料 * 2020-09-07 修正範例內容 * 2020-09-08 錯字修正 * 2021-05-08 修正 docker network名稱 `local-test` => `local_test` * 2021-09-06 1. 修正 資料夾名稱 `traefik` => `traefik_data` 2. 修正 mkcert 指令 3. 修改 traefik 使用版本 `2.2` => `2.5` * 2025-09-08 修正開源專案 `stonehenge`的連結 ## 前言 此篇感謝公司前輩**Rex**的技術分享與指導 ## 動機 1. 本機上需要使用`SSL` + `Domain Name`進行測試時 2. 80,443 port 的複用,減少port衝突 3. 減少修改 `hosts`檔案的麻煩 4. 使用 `VS2019`整合`docker-compose` debug環境 ### 建置環境 1. Win10 1909 2. Docker Desktop ### 使用工具/應用 1. Docker 19.03 與 Docker-Compose 1.26.2 > 使用 容器化的方式建置乾淨的開發環境 2. Traefik 2.2 > 動態的反向代理器,須注意 1.7 與 2.x 的設定方式不相容,此篇相關設定均以 2.2以上的版本為主 3. Dnsmasq > 作為本地DNS,用來引導domain name,以及取代修改hosts檔案 4. mkcert > 產生本機測試用途之可信任的SSL檔 5. Visual Studio 2019 > 本篇舉例用的IDE,也可以使用其他有整合docker開發環境的IDE ## 建構流程 * 為求本機環境乾淨,本文所用到之服務一律使用Docker架設 ### 創建測試用的docker network ```shell docker network create local_test ``` ### 創建docker-compose 專案目錄以及資料結構 ```shell mkdir local-dev cd local-dev # 榮果有安裝VS Code,可直接使用VSCode進行 # code . ``` ### 設定 .env 檔案內容 在`local-dev`目錄下建立 `.env`檔 `.env`檔內容如下 ```shell # Develop env. settings TESTING_DOMAIN=local-test.sh # TESTING_NETWORK需與創建的 docker network一致 TESTING_NETWORK=local_test TESTING_HTTP_PORT=80 TESTING_HTTPS_PORT=443 # Traefik settings TRAEFIK_VERSION=v2.2 TRAEFIK_DASHBOARD_PORT=8080 ## log level following : DEBUG, PANIC, FATAL, ERROR, WARN, and INFO. TRAEFIK_LOG_LEVEL=info # Dnsmasq settings DNSMASQ_VERSION=latest ## dnsmasq ui 的登入帳密設定,如果嫌麻煩可以空白 DNSMASQ_LOGIN_ACCOUNT=admin DNSMASQ_LOGIN_PASSWORD=admin # Portainer settings PORTAINER_VERSION=1.24.1-alpine ``` ### 建立 docker-compose.yml docker-compose.yml 內容如下 ```yaml version: '3.7' ### ====== Network ====== ### networks: default: name: "${TESTING_NETWORK}" external: true ### ====== Services ====== ### services: #### ====== Traefik ====== #### traefik: # The official v2.0 Traefik docker image image: traefik:${TRAEFIK_VERSION} container_name: traefik restart: always # Enables the web UI and tells Traefik to listen to docker command: ## api - --api.debug=true - --api.dashboard=true - --api.insecure=true - --serversTransport.insecureSkipVerify=true ## logs - --log.level=${TRAEFIK_LOG_LEVEL} ## entry points - --entryPoints.http.address=:80 - --entryPoints.https.address=:443 ## provider ### docker provider - --providers.docker=true - --providers.docker.endpoint=unix:///var/run/docker.sock - --providers.docker.exposedByDefault=false - --providers.docker.defaultRule=Host(`{{ trimPrefix "/" .Name }}.${TESTING_DOMAIN}`) - --providers.docker.network=${TESTING_NETWORK} ### file provider - --providers.file.watch=true - --providers.file.directory=/etc/traefik/dynamic/ ports: # The HTTP port - "${TESTING_HTTP_PORT}:80" # The HTTPS port - "${TESTING_HTTPS_PORT}:443" # Traefik DashBoard - "${TRAEFIK_DASHBOARD_PORT}:8080" volumes: # So that Traefik can listen to the Docker events - '//var/run/docker.sock:/var/run/docker.sock' - './traefik-data/acme.json:/acme.json:rw' - './traefik-data/dynamic-configs/:/etc/traefik/dynamic/:rw' - './traefik-data/certs/:/ssl/:ro' labels: - "traefik.enable=true" - "traefik.http.routers.traefik.rule=Host(`traefik.${TESTING_DOMAIN}`)" - "traefik.http.routers.traefik.entrypoints=https" - "traefik.http.routers.traefik.tls=true" - "traefik.http.services.traefik.loadbalancer.server.port=8080" - "traefik.http.routers.traefik.service=api@internal" # catchall router - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)" - "traefik.http.routers.http-catchall.entrypoints=http" - "traefik.http.routers.http-catchall.middlewares=redirect-to-https" # middleware redirect - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" # Dynamic Configuration #### ====== Portainer ====== #### portainer: image: portainer/portainer:1.24.1-alpine container_name: "portainer" restart: unless-stopped command: |- --no-auth -H unix:///var/run/docker.sock --logo "${LOGO_URL}" volumes: - //var/run/docker.sock:/var/run/docker.sock ports: - "${PORTAINER_UI_PORT}:9000" labels: - "traefik.enable=true" - "traefik.http.routers.portainer.rule=Host(`portainer.${TESTING_DOMAIN}`)" - "traefik.http.routers.portainer.entrypoints=https" - "traefik.http.routers.portainer.tls=true" - "traefik.http.services.portainer.loadbalancer.server.port=9000" #### ====== Dnsmasq ====== #### dns: image: jpillora/dnsmasq hostname: dns.${TESTING_DOMAIN} container_name: dns-local restart: always cap_add: - NET_ADMIN ports: - '53:53/udp' - "${DNSMASQ_UI_PORT}:8080" command: - "--address=/.${TESTING_DOMAIN}/127.0.0.1" environment: - HTTP_USER=${DNSMASQ_LOGIN_ACCOUNT} - HTTP_PASS=${DNSMASQ_LOGIN_PASSWORD} volumes: - './etc/dnsmasq.conf:/etc/dnsmasq.conf' - './etc/resolv.conf:/etc/resolv.conf' labels: - "traefik.enable=true" - "traefik.http.routers.dnsmasq.rule=Host(`dns-ui.${TESTING_DOMAIN}`)" - "traefik.http.routers.dnsmasq.entrypoints=https" - "traefik.http.routers.dnsmasq.tls=true" - "traefik.http.services.dnsmasq.loadbalancer.server.port=8080" #### ====== whoami ====== #### (testing web host) whoami: image: containous/whoami container_name: whoami restart: always labels: - "traefik.enable=true" - "traefik.http.routers.whoami.rule=Host(`whoami.${TESTING_DOMAIN}`)" - "traefik.http.routers.whoami.entrypoints=https" - "traefik.http.routers.whoami.tls=true" ``` ### 設定 traefik 動態配置 traefik.dynamic.yaml 內容如下: ```yaml tls: certificates: - certFile: /ssl/local-test.sh.crt keyFile: /ssl/local-test.sh.key ``` ### 使用 mkcert產生 SSL 雖然traefik本身支援自動申請Let's Encrypt的SSL,但用於local的環境下請不要使用Let's Encrypt,詳細緣由請參考[Let's Encrypt官方說明](https://letsencrypt.org/zh-tw/docs/certificates-for-localhost/) 安裝mkcert,會使用套件管理工具 `chocolatey`,如果尚未安裝過,請先在`管理員權`下powershell下執行下面命令: ```shell # PowerShell Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) ``` 開始安裝mkcert ```shell # PowerSell choco install mkcert -y ``` 建立SSL ```shell # 當前目錄於 local-dev mkdir -p traefik-data/certs cd traefik-data/certs # 將此目錄註冊至系統的可信任SSL位置,如果之浩此目錄移至其他位置需要重新註冊 mkcert -install # 建立SSL ,使用 -cert-file 與 -key-file 將齣齣檔rename, domain 必須跟 .env 檔案中的 TESTING_DOMAIN設定一致 mkcert -cert-file local-test.sh.crt -key-file local-test.sh.key "*.local-test.sh" ``` 須注意,由 mkcert 創建的 *.local-test.sh 僅能支援 1 level domain 例如: `app.local-test.sh` (OK) `app.demo.local-test.sh` (ERROR) ### 設定 dnsmasq 配置 建立目錄 etc ```shell mkdir etc cd etc ``` 在 etc目錄下建立 `dnsmasq.conf` 與 `resolv.conf` > dnsmasq.conf : 作為 dnsmasq ip跟domain對應等相關設定 > resolv.conf : 作為 dnsmasq 外部DNS位置的設定,內容可以隨喜好增減 dnsmasq.conf 內容如下: ```shell #dnsmasq config, for a complete example, see: # http://oss.segetech.com/intra/srv/dnsmasq.conf #log all dns queries log-queries #don't use hosts nameservers strict-order resolv-file=/etc/resolv.conf # #explicitly define host-ip mappings # since using command line "--address=/.${TESTING_DOMAIN}/127.0.0.1", so you can ignore this # address=/local-test.sh/127.0.0.1 ``` resolv.conf 內容如下: ```shell # Cloudflare nameserver 1.1.1.1 # Google nameserver 8.8.8.8 # HiNet nameserver 168.95.1.1 ``` ### 設定 Windows DNS配置(有線、無線都需要各自設定) 以下是Windows 10 繁體中文環境所做的設定,每台電腦的網路介面名稱可能不盡相同 Windows上的網路介面名稱請使用下列指令查詢。 ```shell netsh interface ip show config ``` 注意!以下command皆需要管理員權限。 設定 `Wi-Fi` 的DNS,將 127.0.0.1 加入`Wi-Fi`介面的DNS清單,並將順序設為第一個 ```shell netsh interface ip add dnsservers "Wi-Fi" 127.0.0.1 index=1 ``` 設定 `乙太網路` 的DNS,將 127.0.0.1 加入`乙太網路`介面的DNS清單,並將順序設為第一個 ```shell netsh interface ip add dnsservers "乙太網路" 127.0.0.1 index=1 ``` ### 啟動 docker-compose並測試 完整的資料目錄架構如下: * 黑色是資料夾 * 藍色是檔案 * 綠色是註解 ```plantuml @startuml salt { {T + local-dev <color:green> #專案所在的資瞭夾 ++ etc +++ <color:blue> dnsmasq.conf <color:green>#dnsmasq的設定檔 +++ <color:blue> resolv.conf <color:green>#為dnsmasq設定外部的DNS位置 ++ traefik-data +++ certs <color:green>#存放SSL檔案的資料夾 ++++ <color:blue> local-test.sh.crt <color:green>#由mkcert建立 ++++ <color:blue> local-test.sh.key <color:green>#由mkcert建立 +++ dynamic-configs ++++ <color:blue> traefik.dynamic.yml <color:green>#由traefik的動態設定檔,SSL的位置必須在此設定 ++ <color:blue> .env ++ <color:blue> docker-compose.yml } } @enduml ``` 啟動並測試 ```shell # 進入 local-dev目錄 # 啟動 docker compose services docker-compose up -d # 檢查是否有啟動錯誤的服務 docker-compose logs # 測試網路設定 curl https://whoami.local-test.sh ``` 如果上面指令皆順利執行,就可以開啟瀏覽器進入下列頁面: * [Traefik 管理介面](https://traefik.local-test.sh) * [Dnsmasq 設定介面](https://dns-ui.local-test.sh) * [Portainer 管理介面](https://portainer.local-test.sh) 如果遇到domain name問題,請改用localhost:port的方式進入個服務的管理介面做調整(個服務的port皆使用本篇預設為主) * [Traefik 管理介面 - localhost:8080](http://localhost:8080) * [Dnsmasq 設定介面- localhost:8888](http://localhost:8888) * [Portainer 管理介面- localhost:9000](http://localhost:9000) --- ### 與 `Visual Studio 2019` Debug環境整合 本篇文章撰寫時的VS2019版本:VS2019 16.7.2 #### 建立 ASP .NET core 3 專案 #### 加入Docker-Compose支援,並將起始專案設為docker-compose 1. 選擇 [ 新增 > 容器協調器支援]。 [ Docker 支援選項 ] 2. 對話框出現後,選擇 [ Docker Compose]。 詳細內容請參考[微軟官方教學](https://docs.microsoft.com/zh-tw/visualstudio/containers/tutorial-multicontainer?view=vs-2019) #### 修改 `docker-compose.override.yml` 修改後的結果如下: ```yml version: '3.4' services: webapplicationdockercomposedemo: environment: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_URLS=https://+:443;http://+:80 volumes: - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro # 設定label labels: - "traefik.enable=true" - "traefik.http.routers.app.rule=Host(`app.local-test.sh`)" - "traefik.http.routers.app.entrypoints=https" # should be enable tls, then just can using https for traefik - traefik.http.routers.app.tls=true - "traefik.http.services.app.loadbalancer.server.scheme=https" # you should be set 443 port ,if not ,it will connect 80 port, then will be error - "traefik.http.services.app.loadbalancer.server.port=443" - "traefik.docker.network=local_test" # 設定 network networks: - local_test #必須設定設定 networks: local_test: external: true ``` #### 修改 docker-compose.dcproj 該檔案位於dotnet專案目錄底下 修改後的內容如下: ```xml <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk"> <PropertyGroup Label="Globals"> <ProjectVersion>2.1</ProjectVersion> <DockerTargetOS>Linux</DockerTargetOS> <ProjectGuid>4711cffe-8e4c-4077-8578-e91d5b9398f4</ProjectGuid> <DockerLaunchAction>LaunchBrowser</DockerLaunchAction> <DockerServiceName>webapplicationdockercomposedemo</DockerServiceName> </PropertyGroup> <PropertyGroup> <!-- 將DockerServiceUrl內容修改成與docker-compose.yml中設定的host相同 --> <DockerServiceUrl>https://app.local-test.sh</DockerServiceUrl> </PropertyGroup> <ItemGroup> <None Include="docker-compose.override.yml"> <DependentUpon>docker-compose.yml</DependentUpon> </None> <None Include="docker-compose.yml" /> <None Include=".dockerignore" /> </ItemGroup> </Project> ``` #### F5 啟動debug 瀏覽器順利進入到demo畫面及代表設定成功 ## 結語 此篇文章主要以網路架構的方式,配合docker建置開發環境。 此方式雖然方便,但由於架設了不少服務,會比較吃系統資源,如果環境條件不許可, 可以使用 `快速指令修改hosts檔案` + `mkcert來解決開發需求` ## 後記 目前GitHub上已有類似概念的開源專案[stonehenge](https://github.com/druidfi/stonehenge)可供參考(2020-09-02追加) > stonehenge旨在解決網路開發人員的基本問題:如何盡可能輕鬆地進行本地環境開發。 > stonehenge為您提供多個項目的共用開發環境。它將處理專案的路由和本地域,以及開箱即用的這些域的 SSL 證書。 特色: 1. 整合了 traefik, portainer, mailhog, ssh-agent 等服務 2. 目前(2020-09-02)只能在 Linux, MacOS, WSL2環境上使用