# 在digitalocean土炮一個用多少付多少的Minecraft Server 小小備註:整篇文章廢話很多,要看實作可以直接轉到[實作開始!](#實作開始!) ## 前言 - 關於MC開服那檔事 對於一個資深的Minecraft玩家,自己開一個多人伺服器可說是必經之路 從最早期"公開至局域網+hamachi",接著學會用offical server開服 在看到"XXX已加入遊戲"的訊息時,都是莫名的感動 ![](https://i.imgur.com/U4ws6Mu.jpg) ▲欸不是,可以先不要嗎 可是當伺服器開久了,問題就來了,你的好麻吉如果想要在半夜上線掛刷怪塔,你就勢必在睡前搞好伺服器才去睡覺,電腦閃亮亮的RGB影響睡眠不說,還會時不時被突然增大的風扇聲驚醒。還要怕哪天友誼的小船說翻就翻的時候,被朋友突如其來的TNT炸服搞的電腦突然發爐 ![](https://i.imgur.com/0tTVhwn.gif) ▲真。燒機 就在這時!! 雲端主機這個概念就像救世主般降臨...... 粗淺的說就是主機供應商在世界的某處架設了許多電腦,並且讓人們付費然後透過網路連線使用,這麼一來租用雲端主機來架設遊戲伺服器後,就不需要時時刻刻操勞自己的電腦,讓它又要跑遊戲又要開著伺服器了 目前網路上有許多廠商都有提供類似雲端虛擬主機的服務,像是Amazon的AWS、Microsoft的Azure、Google的GCP等等...科技巨頭們提供的雲端服務中就包含虛擬主機的項目,這些公司提供了不只是簡單的主機,還包括了許多可以自訂的選項或設定。當初高中第一次碰linux系統就是跑去註冊了GCP要來開MC伺服器,但由於要設定的東西實在太多了,以至於第一次就算看著教學也是弄了很久才成功 除開以上這幾家的選擇,也有公司提供相對比較簡單虛擬主機(虛擬專用伺服器)服務,例如今天要實作的平台DigitalOcean等 最後則是最簡單選擇,就是用現成的MC伺服器託管,其中不乏有免費的選擇,像是蠻有名Aternos或是也可以選擇其他要付費但相對資源較豐富的託管服務。這種通常都會在他們的網站上就可以幾乎所有伺服器的設定,但相對的就會比上面說的那些再貴一點點啦 以上就是有關一些在雲端上開服的簡介啦,接著就是要進到關於錢的部分 ## 使用者付費?? 可4我是學生捏QQ 每個還在摸索這個世界的學生,都是科技巨頭眼中寶貴的潛在客戶,所以總是會提供各種優惠給在學學生,試圖讓他們對自己公司的產品產生依賴性,並且在之後成為一個忠實的用戶,其中最多元的優惠整合包就屬GitHub的**GitHub Student Developer Pack**了。人說玩github的學生不會變壞,各家公司當然是雙手奉上滿滿試用品到你眼前,試圖要拉攏這些好學生 ![](https://i.imgur.com/OaUZpmL.jpg) [GitHub Student Developer Pack](https://education.github.com/pack) 只要使用學校信箱驗證,就可以享受這個整合包裡的試用或是優惠 其中Cloud這個標籤裡就有像是Microsoft Azure或是DigitalOcean這類可以使用雲端主機的公司(想要架站的也可以試試Heroku喔) 而其中Microsoft Azure提供的是在學期間每年提供$100美金的試用額度,看起來真的有夠香,但是下段就會說明為甚麼放棄它而轉去DO ## 用要付錢 沒用也得照付 其實想想這也是合理,像是固定IP、永久儲存體之類的東西,幾乎所有供應商都是要持續收費的,但是像CPU、RAM這些可以拿去給其他人用的東西就可以在你不使用時暫停收費,azure或是AWS的的都有提供spot instance、也就是當你需要用的時候再把資源分配給你、再開始算錢(但當你付的錢不夠多的時候機器還會被別人搶走喔)。 就當我開開心心的要拿azure的100美金去開spot instance的時候,不管怎麼選,從一顆cpu配2GB RAM一直到30幾顆配好幾百GB RAM,通通都不給創建,[上網爬文](https://www.reddit.com/r/cloudygamer/comments/la8exk/microsoft_azure/)才發線大家都創建失敗......哭阿,浪費我一整個晚上,後來就憤而轉向DigitalOcean的懷抱 剛轉戰到DigitalOcean後發現這邊收費更直接,不管關機還開機狀態,連CPU、RAM的租用費都要持續的付,就這樣這個企劃延宕了好一陣子。直到某天我發現了DigitalOcean有個功能叫snapshot,可以把主機磁碟暫存下來成一個image檔案,並且可以使用那個image創造其他一模一樣虛擬機 這!!!難道說!!! ![](https://i.imgur.com/kaVRqQP.gif) ## 用discord bot控制PAYG(Pay As You Go)的伺服器構想 仔細去看看snapshot之後發現,這根本就是讓我們自己土炮PAYG伺服器的好工具啊!! 規劃了一下之後,想出了以下的思路: ps.在DigitalOcean中稱VM叫做droplet,但之後我都會以VM作為稱呼喔 ![](https://i.imgur.com/D97Az8v.jpg) ▲開啟伺服器 ![](https://i.imgur.com/hzt1VZI.jpg) ▲關閉伺服器 在想以上做法的時候,會發現其實伴隨著很多優點,像是: * 如果不刪除之前的snapshot,可以直接作為伺服器備份,回朔極其簡單 * 儲存snapshot所需付出的費用低廉(0.05USD/GB per month,計費周期為1小時) * 開/關伺服器只要在Discord上就可執行,就算自己不在,其他玩家也可以透過DC指令直接開來玩 * DigitalOcean的網路傳輸費用不是從零計算,創建droplet就會伴隨免費額度 **自己試算之後,如果一個月開200個小時,外加10個snapshot(一個5GB計)當作備份,一個月只要付8.452美金(237台幣),而且還有100美金的額度可以讓我們慢慢扣!** ![](https://i.imgur.com/Nbxpnba.gif) ▲BIG POG! ## 實作開始! ### Part.1 Discord Bot 實作階段我們就跟著上面的圖一步一步走吧 首先要搞個discord bot,就這麼剛好,我在heroku上有一隻運行中的bot,直接拿來加上我們要的功能吧,那這裡就不從頭講到尾了,要看[整個程式碼](https://github.com/IamSkyBlue/orna_discord_bot/blob/master/mcserver.py)可以直接去Github,我們直接挑重點講~ ps.我的code看起來非常脆弱,某個環節出的差錯就會大炸裂,但我還是想多花點時間在minecraft本身XD將就點吧~ ``` # Line 124 def create_droplet(self): self.update() # 重新拉snapshot資料,例如snapshot id url = "https://api.digitalocean.com/v2/droplets" payload = { "name": "SkyLandServer", # VM名字 "region": "sgp1", # VM地理位置 sgp1:新加坡 "size": "s-2vcpu-4gb", # VM配置 2CPU+4GB RAM "image": self.id, # 創建VM所使用的snapshot id "ssh_keys": SSH_KEYS, # 之後可以連線到VM的SSH KEY } response = requests.post(url, data=json.dumps(payload), headers=HEADERS) if response.status_code == 202: return True ``` 以上程式碼就是在snapshot這個class底下用self.update()取得最新的snapshot的id,並且加上一些必要資料,然後post過去創建一個新的VM,ssh_keys必須在[這裡](https://cloud.digitalocean.com/account/security)**事先**設定,不然要連線就只能用他網頁的assess panel了,創建VM的API詳細格式[在這裡](https://developers.digitalocean.com/documentation/v2/#create-a-new-droplet) ``` # Line 58 def shutdown_and_make_snapshot_and_delete(self): self.update() url = "https://api.digitalocean.com/v2/droplets/" + str(self.get_id()) + "/actions" payload = {"type": "shutdown"} response = requests.post(url, data=json.dumps(payload), headers=HEADERS) content = json.loads(response.content) action = content["action"]["id"] url = "https://api.digitalocean.com/v2/actions/" + str(action) counter = 1 while True: response = requests.get(url, headers=HEADERS) content = json.loads(response.content) if content["action"]["status"] == "completed": break elif counter > 120: return False else: counter += 1 time.sleep(5) ``` 由於這個method有點長,分段解釋一下,第一部分就是將VM關機,並且取得"關機"這個動作(action)的id,並且定時查詢這個action是否完成,若超過10分鐘還沒關機成功就報錯,基本上30秒內就能完成喔,關閉VM的API詳細格式[在這裡](https://developers.digitalocean.com/documentation/v2/#shutdown-a-droplet) ``` # Line 78 url = "https://api.digitalocean.com/v2/droplets/" + str(self.get_id()) + "/actions" payload = {"type": "snapshot", "name": "SkyLand_Snapshot_" + datetime.now().strftime("%Y-%m-%d--%H-%M-%S")} response = requests.post(url, data=json.dumps(payload), headers=HEADERS) content = json.loads(response.content) action = content["action"]["id"] url = "https://api.digitalocean.com/v2/actions/" + str(action) counter = 1 while True: response = requests.get(url, headers=HEADERS) content = json.loads(response.content) if content["action"]["status"] == "completed": break elif counter > 60: return False else: counter += 1 time.sleep(10) ``` 成功將VM關機後,接著就是用這個VM創建一個snapshot image檔,這個步驟可能會花上數分鐘喔,創建snapshot的API詳細格式[在這裡](https://developers.digitalocean.com/documentation/v2/#snapshot-a-droplet) ``` # Line 96 url = "https://api.digitalocean.com/v2/droplets/" + str(self.get_id()) response = requests.delete(url, headers=HEADERS) if response.status_code == 204: return True else: return False ``` 成功創建snapshot後,這個VM就功成身退了,我們就可以直接把他刪掉,相關的費用就會停止計費啦,刪除VM的API詳細格式[在這裡](https://developers.digitalocean.com/documentation/v2/#delete-a-droplet) 接著是關於監測minecraft伺服器狀態的程式碼,也是挑一段來講就好,其他概念都差不多 ``` # Line 153 @commands.command(name="mc-status", help="伺服器狀態 使用方法: ~mc-status") async def mc_status(self, ctx, *args): if ctx.guild.id not in ALLOW_GUILD: await ctx.send("這是私人的伺服器喔~抱歉") return if not droplet.get_id(): await ctx.send("伺服器是關閉狀態") return else: try: server = get_server() query = server.query() online_people = "\n".join(query.players.names) except socket.timeout: await ctx.send("目前伺服器正在開啟中喔,如果已經等很久還沒開好請聯絡SkyBlue") return except Exception as e: await ctx.send("發生問題,請聯絡SkyBlue" + str(e)) return await ctx.send("伺服器開著喔") if query.players.names: await ctx.send("目前有這些人在線上:\n" + online_people) else: await ctx.send("但沒有人在線上QQ") ``` 透過[mcstatus](https://github.com/Dinnerbone/mcstatus)提供的server class,設定好IP之後就可以直接取得伺服器狀態,這邊有幾點要注意 * 如果要使用query()來看有哪些人在伺服器裡,在minecraft伺服器的server.properties要把enable-query打開 * 每次重新創建VM都會有新的IP,記得不要寫死,要讓他每次都可以更新成新的IP ### Part.2 創建VM 我們處理完了機器人,接著就是VM的部分啦,首先要到[這裡](https://cloud.digitalocean.com/droplets)創建VM(Droplet) ![](https://i.imgur.com/4mgabL7.jpg) ▲第一次手動創建我們選現成的image,之後自動化了才會讓機器人去選我們自己創的snapshot ![](https://i.imgur.com/Qd2o9DU.jpg) ▲機器可以根據自己的需求選擇,因為我這次開的是模組伺服器,所以就選了RAM大一點的,一般原版伺服器2GB就可以玩得蠻順暢的喔 ![](https://i.imgur.com/Ufs6KnN.jpg) ▲伺服器位置我們選最近的新加坡 ![](https://i.imgur.com/QnvEje5.jpg) ▲驗證模式選SSH會比較安全,那這邊就會用到之前[在這裡設定的SSH Key](https://cloud.digitalocean.com/account/security) 剩下的不是要加錢就是不太重要,就依自己的喜好選擇吧,創建成功後會出現以下控制面板 ![](https://i.imgur.com/MTd3xea.jpg) 接著可以選擇透過之前設定好的SSH Key用SSH軟體遠端連線(推薦bitvise! 好用!) 或是透過Access中的Console access直接連線 ![](https://i.imgur.com/Y4sPoUV.jpg) ### Part.3 systemd 接著就是要來解決怎麼開啟伺服器啦,首先我們要先自己連線到VM去設定,這裡我們用systemd自己寫一個service,那這樣他就會在開機時自行啟動喔 1. 第一步,新增名為minecraft的使用者和群組 ``` groupadd -r minecraft useradd -r -g minecraft minecraft ``` 2. 在`/home/minecraft/`目錄下創建一個資料夾,我是取名offical 也就是`/home/minecraft/offical`,接著把伺服器檔案放進去,接著就像在自己電腦開服一樣把該設定的`server.properties`設定一下,要注意的是`enable-query`、`enable-rcon`都打開和`rcon.password`要設定下面的做法才會成功喔,成功開啟伺服器後再前往下一步 3. 接著使用編輯器`nano /etc/systemd/system/minecraft.service`創建service檔案 雖然說[minecraft wiki上有較專業的範例](https://minecraft.fandom.com/wiki/Tutorials/Server_startup_script)但他的做法我一直無法正常啟動,所以就照著其他作法寫了一個比較簡單的 這個檔案中使用的`startserver.sh`和`mcrcon`在下方會解釋 ``` [Unit] Description=Minecraft server After=local-fs.target network.target [Service] WorkingDirectory=/home/minecraft/offical User=minecraft Group=minecraft Type=forking ExecStart=/usr/bin/screen -h 1024 -dmS minecraft ./startserver.sh ExecStop=/usr/local/bin/mcrcon -H localhost -p YOUR_PASSWORD save-all stop ExecStop=/bin/sleep 20 [Install] WantedBy=multi-user.target ``` `startserver.sh`是放在`/home/minecraft/offical`中啟動伺服器的檔案,大概是長這樣子: ``` #!/bin/bash java -Xms1024M -Xmx4096M -jar 伺服器檔案名.jar nogui ``` [mcrcon](https://github.com/Tiiffi/mcrcon)則是簡化和加強伺服器管理的軟體,可以照著github上的說明安裝 創建完service檔案後就來測試看看可不可以成功啦 ``` systemctl start minecraft #啟動service systemctl stop minecraft #關閉service systemctl status minecraft #查看service狀態 ``` start之後可以切換到minecraft使用者看一下伺服器狀況:`su minecraft`然後`screen -r` 確認可以正常啟動/關閉後,就可以啟用service啦,這樣每次開機都會自己開啟service ``` systemctl enable minecraft ``` ## 成果 經過一番努力之後,我們終於可以實現用Discord bot開關伺服器啦 ![](https://i.imgur.com/JI7AqDU.jpg) ▲成功啦!!! ## 結語 其實這次的實作部分都是我成功做完後才來寫的,中間不知道掉進兔子坑多少次,也有試過一些其他作法,但最後成功看到discord bot回傳訊息真的是超開心的,那如果中間discord bot的地方我跳太快看不懂,也可以嘗試著只做digital ocean VM的部分,這樣作業量會少很多喔 ## 資源連結 * discord bot跟本次計畫有關的code: [https://github.com/IamSkyBlue/TC_orna_bot/blob/59aa67d262313a48fdd53857efedf28624d1e4b7/mcserver.py](https://github.com/IamSkyBlue/TC_orna_bot/blob/59aa67d262313a48fdd53857efedf28624d1e4b7/mcserver.py) * mcstatus-查看伺服器狀態: [https://github.com/Dinnerbone/mcstatus](https://github.com/Dinnerbone/mcstatus) * mcrcon-透過rcon操控伺服器: [https://github.com/Tiiffi/mcrcon](https://github.com/Tiiffi/mcrcon) * DigitalOcean API doc(兩個差不多): [https://docs.digitalocean.com/reference/api/api-reference/](https://docs.digitalocean.com/reference/api/api-reference/) [https://developers.digitalocean.com/documentation/v2/](https://developers.digitalocean.com/documentation/v2/) * systemd server start up: [https://minecraft.fandom.com/wiki/Tutorials/Server_startup_script](https://minecraft.fandom.com/wiki/Tutorials/Server_startup_script)