# Docker ## Docker 歷史,什麼是 Docker 2008年,Solomon Hykes 和他的朋友Kamel Founadi、Sebastien Pahl 共同創立了一家名為DotCloud 的公司,目標是利用一種叫做容器的技術來創建他們稱作是“大規模的創新工具”:任何人都可以使用的編程工具。 2010年,DotCloud 獲得了創業孵化器 Y Combinator 的支持,並開始吸引到一些真正的投資,在接下來的3年中,dotCloud 內部孵化了一款名為 Docker 的產品。 2013年3月 Docker 創始人 Solomon Hykes 在 PyCon 大會上的演講中首次公開介紹了 Docker 這一產品。在2013年 PyCon 大會之後,Docker 的 "創新式鏡像格式" 以及 "容器運行時" 迅速成為社區、客戶和更廣泛行業的實際標準和基石。 Docker 的強大之處在於它通過可移植的形式和易於使用的工具在應用程序和基礎設施之間創造了獨立性。其結果是,Docker 將容器技術大眾化,並解決了困擾數百萬開發人員的 "matrix from hell" 問題,使容器技術成為主流。 2013年3月20日,DotCloud 發布了 Docker 的首個版本,並將 Docker 源碼進行開源,開源地址。 2013年9月,紅帽公司成為 Docker 的主要合作夥伴,利用 Docker 來驅動他的 OpenShift 雲業務。隨後,谷歌、亞馬遜以及 DigitalOcean 也迅速的在其云服務平台提供了 Docker 的支持。主流雲廠商的加入,加速 Docker 的發展進度。 隨著Docker 技術的開源,此項技術能夠將Linux 容器中的應用代碼打包並輕鬆的在服務器之間遷移,Docker 技術瞬時的風靡了全球,2013年底公司名稱由"DotCloud Inc" 更改為"Docker Inc ",並全力的主攻到Docker 項目開發中 Docker 是一種開源平台和工具,用於輕鬆地創建、部署和運行應用程序和服務在容器中。它提供了一種容器化技術,允許開發人員將應用程序及其依賴項打包到一個獨立的、可移植的容器中。這些容器可以在不同的環境中運行,而不必擔心環境之間的差異性,從而實現了應用程序的可移植性和一致性。 以下是 Docker 的主要特點和概念: 容器化:Docker 使用容器技術,將應用程序及其運行時環境、依賴項和配置打包到一個容器中。這個容器是一個獨立的、隔離的單元,具有自己的文件系統和資源限制。容器可以在任何支持 Docker 的環境中運行,無論是開發人員的本地計算機、測試環境還是生產服務器。 輕量級:Docker 容器非常輕量級,因為它們共享主機操作系統的內核,而不需要額外的虛擬化層。這使得容器的啟動和停止速度非常快,佔用的系統資源也較少。 可移植性:由於 Docker 容器包含了應用程序及其所有依賴項,因此它們在不同的環境中表現一致,從而實現了可移植性。開發人員可以在本地構建和測試容器,然後將它們部署到生產服務器上,而無需擔心環境差異導致的問題。 版本控制:Docker 允許將容器鏡像版本化,這意味著你可以隨時回滾到先前的版本,或者創建新的版本來滿足不同的需求。 自動化部署:Docker 可以與自動化工具(如容器編排工具 Kubernetes)一起使用,實現自動化部署、伸縮和管理容器化應用程序。 生態系統:Docker 生態系統豐富,有大量的容器鏡像可用於各種應用程序和服務,這些鏡像可以加速應用程序的開發和部署過程。 ## 為什麼要使用 Docker (一)在主機上快速部署 由於 Docker 容器具有跨平台可攜性,並且共享作業系統中的資源,它相較虛擬機更為輕量。在相同資源下,能執行容器數大約是虛擬機的 5 到 10 倍,能夠提高開發者的工作效率並節省資金。 (二)版本控管和物件重複使用 Docker 讓使用者能夠儲存不同的版本的歷史紀錄,使用者能夠依照自己的喜好回滾(Roll Back)至最合適的版本。開發人員只需將環境設定好,就能使用先前保存好的容器,達到重複使用的目的。 (三)更有效率的 CI/CD 流程 Docker 幫助開發人員在任何環境下測試程式碼,協助他們在應用程式開發初期就能找出錯誤。利用 Docker 易與其他工具整合的優勢,開發人員能將 GitHub 和 Jenkins 整合至 Docker 中。只需將程式碼交付至 Github,再由 Jenkins 進行自動化佈署,就能夠快速地建立映像檔。開發者還能將映像檔上傳至 Docker Hub 來解決不同版本間環境不相容的問題。Docker 能夠簡化開發部署流程、減少環境建置的時間,以及讓開發人員同時執行多個測試。  Docker 結合 CI/CD 流程進行自動測試與部署 圖片來源 開發人員讓 Docker 融入 CI 流程的一種方式是,開發者在建立完應用程式後,透過 CI 伺服器建立 Docker 映像檔,再將應用程式裝入映像檔,最後再上傳到 Docker Hub。開發者能夠在另一台主機上,不論是在 QA、DEV 或正式環境,從 Docker Hub 上存取映像檔並運行容器,在 CI 伺服器中,開發者甚至能在創建映像檔的過程中,完成編輯和測試。 ## 安裝 Docker 依照官方的步驟進行安裝: https://docs.docker.com/engine/install/ 若要查看作業系統的版本可使用以下指令 ``` $ cat /etc/lsb-release ``` 安裝完 Docker 後使用以下指令確認安裝的版本 ``` $ sudo docker version ```  若不使用 sudo (root 權限),可以將使用者加入至 docker group ``` $ sudo groupadd docker $ sudo usermod -aG docker $USER $ newgrp docker # 執行 hello-world 確認 docker 安裝成功 $ docker run hello-world ```  ## 移除 Docker 解除安裝 Docker 之前,請確定您的系統上沒有正在執行的容器。 執行下列 Cmdlet 來檢查執行中的容器: PowerShell ``` docker swarm leave --force docker ps --quiet | ForEach-Object {docker stop $_} ``` 同時也最好先從您的系統移除所有容器、容器映像、網路和磁碟區,然後再移除 Docker。 您可以執行下列 Cmdlet 來完成這項操作: PowerShell ``` docker system prune --volumes --all ``` 接下來,您必須實際解除安裝 Docker。 Windows 10 上的 Docker: 移至 Windows 10 電腦上的 [設定] > [應用程式] 在 [應用程式 & 功能] 底下,尋找 適用于 Windows 的 Docker 移至 [適用於 Windows 的 Docker] > [解除安裝] Windows Server 2016 上的 Docker: 從提升權限的 PowerShell 工作階段中,使用 Uninstall-Package 和 Uninstall-Module Cmdlet,從您的系統移除 Docker 模組及其對應的套件管理提供者,如下列範例所示: PowerShell ``` Uninstall-Package -Name docker -ProviderName DockerMsftProvider Uninstall-Module -Name DockerMsftProvider ``` ## Docker Desktop 收費 https://www.docker.com/pricing/faq/ 免費條件: - 250 人以下的公司,而且年營業額在 10m 美金以下的情況免費 - 個人使用免費 - 教育使用免費 - 非商業的 open source 專案保持免費 由於Docker Desktop開始收費,所以改用Podman或k8s ## Docker vs Podman Podman 是一個無背景程序( Daemonless )的容器引擎,用於在Linux系統上開發,管理和運行容器。容器可以以root或非root使用者模式運行。 為什麼 Red Hat 想要擺脫 Docker Daemon?這是因為使用 Docker Daemon 運行 Docker 有以下這些問題 - 單點故障問題,Docker Daemon一旦死亡,所有容器都將死亡。 - Docker Daemon擁有運行中的容器的所有子進程。 - 所有Docker操作都必須由具有跟root相同權限的用戶執行。 - 構建容器時可能會導致安全漏洞。 因此,Podman 通過直接與 Image Registry,Image 和 Container 進行互動而不是通過背景程序來解決上述問題。而且,Podman 允許用戶在沒有完全 root 權限的情況下運行容器。  引用https://ithelp.ithome.com.tw/articles/10238749 ## Docker Compose @Vincentl92 ### 什麼是Docker Compose Docker Compose是用來定義和執行多容器Docker應用程式的工具。在Compose中要使用YAML檔案設定應用程式的服務。然後,只要執行單一命令,即可從您的設定建立並啟動所有服務。 舉例來說,啟動一個Docker Container會使用docker run指令,若要啟動多個,則需要輸入多次指令,另外需要連結不同的Container時也需要個別使用來連結,這樣在啟動大量Container時就會顯得相當麻煩,因此出現了Docker Compose。 只要寫一個docker-compose.yml,把所有要使用Docker Image寫上去,再把Container之間的關係連結起來,最後只要下 docker-compose up指令,就可以把所有的Docker Container 執行起來。簡單來說,Docker Compose主要用於簡化容器化應用程序的部署和管理。 ### 安裝Docker Compose 安裝Docker Compose的指令如下: ```bash cd /usr/bin wget https://github.com/docker/compose/releases/download/1.18.0/docker-compose-Linux-x86_64 mv docker-compose-Linux-x86_64 docker-compose chmod 755 docker-compose ``` ### 撰寫docker-compose.yaml 基本的yaml大致會分為幾個區塊 ``` version: "3.9" services: ... volumes: ... networks: ... ``` 其中version是必須的,並且要至少建立一個services。volumes和networks則是額外的設置。version是用來確定你所使用的 Docker Compose 版本。services基本上就是 container,有幾個 services 就是有幾個 container。volumes和networks則是共用空間和網路配置,這兩個就是比較進階的應用。 #### Service介紹 如果今天要建立一個網站,我們可以大致拆分為前端、後端、資料庫,我們可以將這三個部分分別作為一個映像檔,並在Service中定義三個不同的Service。 ``` services: frontend: image: my-frontend ... backend: image: my-backend ... db: image: my-database ... ``` #### Networks 和 Volumes介紹 Networks 定義了主機和 containers 之間、以及各個 containers 之間的溝通渠道以及方法。在默認設置中,Docker Compose 會自動建立一個單一的網路,各個 containers 可以加入此網路和彼此溝通,主機也可以透過此網路直接和 container 取得聯繫。 Volumes 是一個在主機上的共享資料夾。透過這個資料夾,不同的 containers 之間可以分享資料,containers 也可以取得主機的資料。什麼時候會需要用到 volume 呢?比如說當我們在 container 裡面做了一些資料處理後,而我們想要將這些更新過的資料儲存下來,這樣我們就可以將這些資料儲存在 volume 中。如果我們不儲存在 volume 中,那麼這些資料就會隨著 container 被刪除而也跟著不見。 ``` version: '3.7' services: frontend: # Dockerfile的路徑 # '.' 代表當前docker-compose.yml所在的路徑 build: . # container port和主機port的配對 ports: - "5000:5000" # 將主機的資料夾mount到container中 volumes: - "/usr/:/user" db: # 來自Docker Hub的映像檔 image: mysql/mysql-server:5.7 # 環境參數 environment: - "MY_USER=user" - "MY_PASSWORD=0123" - "MY_DATABASE=database" ``` build:如果沒有提供映像檔,我們可以透過這個參數來指定這個 container 所要使用的 Dockerfile。.表示當前docker-compose.yml所在的路徑。 ports:container port 和主機 port 的配對。我們可以利用這個 port 來達到主機和容器的溝通。 volumes:這裡的 volumes 和在使用 Docker 時的-v參數是一樣的。在這個例子中,我們將本機上的/usr資料夾搬移到 container 中的/user資料夾。這樣一來,更改後的資料就不會遺失了。 environment:這裡就是在設置環境參數。這些參數在 Container 被啟動與建立時就會被使用。 ## dockerfile Dockerfile用來建立自己的Docker Image ### 範例: ``` FROM centos:7 MAINTAINER jack RUN yum install -y wget RUN cd / ADD jdk-8u152-linux-x64.tar.gz / RUN wget http://apache.stu.edu.tw/tomcat/tomcat-7/v7.0.82/bin/apache-tomcat-7.0.82.tar.gz RUN tar zxvf apache-tomcat-7.0.82.tar.gz ENV JAVA_HOME=/jdk1.8.0_152 ENV PATH=$PATH:/jdk1.8.0_152/bin CMD ["/apache-tomcat-7.0.82/bin/catalina.sh", "run"] ``` ### 說明: FROM,也就是這個 Dockerfile 是繼承哪個 image ,用centos:7作為基底 MAINTAINER: 用來說明,撰寫和維護這個 Dockerfile 的人是誰,也可以給 E-mail的資訊 RUN: RUN 指令後面放 Linux 指令,用來執行安裝和設定這個 Image 需要的東西 ADD: 把 Local 的檔案複製到 Image 裡,如果是 tar.gz 檔複製進去 Image 時會順便自動解壓縮。Dockerfile 另外還有一個複製檔案的指令 COPY 未來還會再介紹 ENV: 用來設定環境變數 CMD: 在指行 docker run 的指令時會直接呼叫開啟 Tomcat Service ### build出image檔 ``` $ docker build -t mytomcat . --no-cach ``` Build 完的結果如下圖:  Build 完 Docker Image 之後,使用 docker images 指令查看是否有 build 成功如下圖  ### 執行 ``` $ docker run mytomcat ``` 要打開 Browser 確認 Tomcat Service 有沒有被執行起來時,發現我們不知道 Docker Container 的 IP,這時侯只能使用 docker exec 進入 docker container查詢 IP 。 要使用 docker exec 指令之前需要先知道 Container 的 ID 所以需要先使用 docker ps 指令查詢 Container ID,如下圖:  有了 IP 之後就可以打開 Browser 輸入 http://172.17.0.2:8080 URL的位置,確認 Tomcat Service 是否有啟動,如下圖:  # **容器(Container)VS 虛擬機(VM)** **VM** 的主要功能是作業系統的虛擬化,**Container**主要的功能是虛擬化操作系統,因此**Container**相較於 **VM** 更容易攜帶,也更高效。 | 功能 | Virtual Machine | Docker Container | |:------------:|:---------------:|:----------------:| | 虛擬化的目標 | 作業系統 | 應用系統 | | 啟動時間 | 久(幾分鐘) | 短(幾秒) | | 容量 | GB | MB | | 耗費資源 | 較多 | 較少 | | 複製相同環境 | 慢 | 快 | | 服務內容 | 大型專案服務 | 多使用於微服務中 | ## **Container 相較於 VM 的優點** * **輕量級**: Docker 容器非常輕量級,因為它們共享主機操作系統的內核,而不像虛擬機一樣需要獨立的操作系統。這使得容器啟動更快,占用的資源更少。 * **快速啟動和停止**: Docker 容器可以在幾秒鐘內啟動和停止,而虛擬機通常需要更多時間。這使得容器適用於需要快速擴展和部署的情況。 * **一致的運行環境**: Docker 確保應用程式在不同環境中具有相同的運行時環境,從而減少了“在我的機器上可以工作”的問題。這有助於提高應用程式的可靠性。 * **易於擴展和管理**: Docker 容器可以使用容器編排工具(如 Kubernetes、Docker Swarm)進行自動化部署、擴展和管理。這使得在大規模和分佈式環境中易於管理和協調容器。 * **版本控制和複製**: Docker 允許將應用程式和其依賴項封裝成一個鏡像,這個鏡像可以版本控制和複製,確保開發、測試和生產環境之間的一致性。 * **易於分發和部署**: Docker 鏡像可以輕鬆分發到不同的環境,包括本地開發機、測試伺服器和雲平台,無需重新配置。 * **跨平台性**: Docker 鏡像可以在支持 Docker 的操作系統上運行,具有跨平台性,無需擔心兼容性問題。 * **社群支持和生態系統**: Docker 擁有龐大的開源社群支持和豐富的生態系統,有大量的容器鏡像和工具可供使用。 * **容器註冊表**: Docker Hub 等容器註冊表提供了方便的鏡像存儲和分享平台,可以輕鬆訪問和分享容器鏡像。 Docker的三個基本觀念 映像檔(Image) 映像檔為一個唯獨的模板,內可包含完整的ubuntu作業系統環境。映像檔可用來快速生產Docker容器,並可重複產生。映像檔可以透過Dockerfile建立,或是開放公開下載的地方取得。 容器(Container) 容器是由映像檔建立出來的實例,Docker即是利用容器來執行關鍵技術,容器可以被啟動、開始、停止、刪除,且容易與容器之間是相互隔離、保證安全的。 倉庫(Repository) 倉庫是用以放置映像檔檔案的場所,每個倉庫可收納多個映像檔,而每個映像檔又有相對應的標籤。倉庫共有公開倉庫(Public)及私有倉庫(Private)兩種形態。而最大的公開倉庫為Docker Hub,裡面存放了大量的現成、實用映像檔供使用者下載。 ## 容器(Container) Docker 利用容器來執行應用。 容器是從映像檔建立的執行實例。它可以被啟動、開始、停止、刪除。每個容器都是相互隔離的、保證安全的平台。 可以把容器看做是一個簡易版的 Linux 環境(包括root使用者權限、程式空間、使用者空間和網路空間等)和在其中執行的應用程式。 *註:映像檔是唯讀的,容器在啟動的時候建立一層可寫層作為最上層。 簡單的說,容器是獨立執行的一個或一組應用,以及它們的執行態環境。換句話說,虛擬機可以理解為模擬執行的一整套作業系統(提供了執行態環境和其他系統環境)和跑在上面的應用。 本章將具體介紹如何來管理一個容器,包括建立、啟動和停止等。 ### 啟動 啟動容器有兩種方式,一種是將映像檔新建一個容器並啟動,另外一個是將終止狀態(stopped)的容器重新啟動。 因為 Docker 的容器實在太輕量級了,使用者可以隨時刪除和新建立容器。 1. 新建並啟動 所需要的命令主要為 docker run。 例如,下面的命令輸出一個 “Hello World”,之後終止容器。 這跟在本地直接執行 /bin/echo 'hello world' 相同, 幾乎感覺不出任何區別。 ``` $ sudo docker run ubuntu:14.04 /bin/echo 'Hello world' Hello world ``` * 下面的命令則啟動一個 bash 終端,允許使用者進行互動。 其中,-t 選項讓Docker分配一個虛擬終端(pseudo-tty)並綁定到容器的標準輸入上, -i 則讓容器的標準輸入保持打開。 ``` $ sudo docker run -t -i ubuntu:14.04 /bin/bash root@af8bae53bdd3:/# ``` 在互動模式下,使用者可以透過所建立的終端來輸入命令,例如 ``` root@af8bae53bdd3:/# pwd / root@af8bae53bdd3:/# ls bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var ``` 當利用 docker run 來建立容器時,Docker 在後臺執行的標準操作包括: * 檢查本地是否存在指定的映像檔,不存在就從公有倉庫下載 * 利用映像檔建立並啟動一個容器 * 分配一個檔案系統,並在唯讀的映像檔層外面掛載一層可讀寫層 * 從宿主主機設定的網路橋界面中橋接一個虛擬埠到容器中去 * 從位址池中設定一個 ip 位址給容器 * 執行使用者指定的應用程式 * 執行完畢後容器被終止 2. 啟動已終止容器 可以利用 docker start 命令,直接將一個已經終止的容器啟動執行。 容器的核心為所執行的應用程式,所需要的資源都是應用程式執行所必需的。除此之外,並沒有其它的資源。可以在虛擬終端中利用 ps 或 top 來查看程式訊息。 ``` root@ba267838cc1b:/# ps PID TTY TIME CMD 1 ? 00:00:00 bash 11 ? 00:00:00 ps ``` #### 背景執行 更多的時候,需要讓 Docker 容器在後臺以背景(Daemonized)形式執行。此時,可以透過新增 -d 參數來實作。 ``` $ sudo docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done" 1e5535038e285177d5214659a068137486f96ee5c2e85a4ac52dc83f2ebe4147 ``` 容器啟動後會返回一個唯一的 id,也可以透過 docker ps 命令來查看容器訊息。 ``` $ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1e5535038e28 ubuntu:14.04 /bin/sh -c 'while tr 2 minutes ago Up 1 minute insane_babbage ``` 要取得容器的輸出訊息,可以透過 docker logs 命令。 ``` $ sudo docker logs insane_babbage hello world hello world hello world . . . ``` ## Docker 指令 [參考:Docker 入門](https://azole.medium.com/webconf2023-docker-%E5%85%A5%E9%96%80-101-fae89170553a) > 備註:這是新版的指令格式,現在開始學 Docker 的朋友,很推薦先試試看新版的格式,雖然比舊版的冗長,但比較有規律、比較好記跟學習。  新舊指令對照 ``` # 註解是舊版指令 # docker pull ashleylai/webconf2023 docker image pull ashleylai/webconf2023 # docker push ashleylai/webconf2023 docker image push ashleylai/webconf2023 # docker rm {containerID or containerName} docker container rm {containerID or containerName} # docker rmi {imageID or imageName} docker image rm {containerID or containerName} # docker run -it --rm -p 3001:3000 ashleylai/webconf2023 docker container run -it --rm -p 3001:3000 ashleylai/webconf2023 # docker ps docker container ls # docker images docker image ls ``` ``` # 列出目前在執行中的 container docker container ls # 列出所有 container docker container ls -a # 列出 image docker image ls # 先把 image 從 DockerHub 或其他 registry 上 pull 下來 docker image pull {image name} # 列出 image 確認 docker image ls ```  ## Docker Image Docker 映像檔是一個模板,用來重複產生容器實體。例如:一個映像檔裡可以包含一個完整的 MySQL 服務、一個 Golang 的編譯環境、或是一個 Ubuntu 作業系統。 透過 Docker 映像檔,我們可以快速的產生可以執行應用程式的容器。而 Docker 映像檔可以透過撰寫由命令行構成的 Dockerfile 輕鬆建立,或甚至可以從公開的地方下載已經做好的映像檔來使用。 舉例來說,如果我今天想要一個 node.js 的執行環境跑我寫好的程式,我可以直接到上 DockerHub 找到相對應的 node.js 映像檔 ,而不需要自己想辦法打包一個執行環境。 ```bash $ sudo docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE ubuntu 12.04 74fe38d11401 4 weeks ago 209.6 MB ubuntu precise 74fe38d11401 4 weeks ago 209.6 MB ubuntu 14.04 99ec81b80c55 4 weeks ago 266 MB ubuntu latest 99ec81b80c55 4 weeks ago 266 MB ubuntu trusty 99ec81b80c55 4 weeks ago 266 MB ... $ sudo docker pull ubuntu:12.04 Pulling repository ubuntu ab8e2728644c: Pulling dependent layers 511136ea3c5a: Download complete 5f0ffaa9455e: Download complete a300658979be: Download complete 904483ae0c30: Download complete ffdaafd1ca50: Download complete d047ae21eeaf: Download complete ``` $ sudo docker build -t="ouruser/sinatra:v2" . ## Docker Daemon 用來執行管理 Docker image、啟動 container、停止 container 的 service,它是一個 long time service。並且有提供 Restful API 給使用者做操作或是顯示一些 Docker container 的狀態訊息。  ## 安裝Docker Desktop on Windows https://mnya.tw/cc/word/1996.html WSL(Windows Subsystem for Linux)是一個在 Windows 上運行 Linux 的子系統,它能讓使用者在 Windows 上使用 Linux 的指令列工具和應用程式。WSL 支援多個 Linux 發行版,包含 Ubuntu、Debian、Kali Linux 等。我們選用 Ubuntu,它是一個廣泛使用的 Linux 發行版,擁有強大的社群和良好的文件支援,可以運行在各種不同的裝置上,包含桌電、伺服器等,另外透過 apt-get 等套件管理器以輕鬆安裝和升級軟體。Docker Desktop 是一個在 Windows 上運行 Docker 的工具,它可以讓使用者在本機環境中運行和管理 Docker 容器。Docker 是一個開源的容器化平台,它可以讓開發者和系統管理員將應用程式和其相依套件打包成容器,並在不同的環境中運行。Docker 可以大幅簡化應用程式的部署和管理,也能提高應用程式的可移植性和彈性。總結來說,WSL 可以讓 Windows 使用者在本機環境中運行 Linux 應用程式,Ubuntu 是一個常用的 Linux 發行版,而 Docker Desktop 則是一個容器化平台,可以幫助使用者更輕鬆地部署和管理應用程式。此影片教大家在 Windows 系統的電腦中完成全部 WSL + Docker Desktop 環境安裝與建置。 https://www.youtube.com/watch?time_continue=40&v=f63h-se6JOU&embeds_referring_euri=https%3A%2F%2Fmnya.tw%2F&source_ve_path=Mjg2NjMsMzY4NDIsMjg2NjY&feature=emb_logo https://www.dotblogs.com.tw/fire/2021/06/04/153043 
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up