Hi all,自上一篇部落格之後,你有沒有自己開啟過 Minecraft Server 找朋友一起冒險了呢?
相信大家都遇到一個很麻煩的問題,就是如果我的 ssh 斷線了的話,我的 Minecraft Server 就會自動斷線了!
這個問題非常的討人厭,其中的原因是因為 ssh 斷線後(可能因為電腦待機、網路不穩或手動關閉的等原因),運行在指令列中的 Minecraft Server 也會隨之中斷,導致 Minecraft 遊戲斷線。
除此之外,如果朋友們通通都下線了,那主機其實可以不用開機的。畢竟如果沒有人在玩,開機還是會持續收費。所以還需要再登入 AWS Console 把我們的 EC2 Instance 關閉。要玩的時候還需要再打開來,也可以節省一點費用。
更麻煩的是,如果目前主機是關閉中,而且你目前沒有辦法登入 AWS Console 帳號把 EC2 開起來,這樣你的朋友們也無法玩了!
為了解決以上問題,我們設計了架構圖如下,首先我們會讓 Minecraft 主機開機時自動打開 Minecraft Server,接著我們可以透過開機/關機的 API (Application Programming Interface)來讓我們不用登入 AWS Cosnole 就控制我們的 Minecraft 主機,接下來因為重開機之後 EC2 的 Public IP 會重新 Assign,所以我們可以透過 API 的方式取得主機 IP。
接下來這篇部落格會專注在教導各位如何保持 Minecraft Server 的開啟,以及透過 API 的方式控制主機。
此篇部落格大綱如下:
1. 安裝 screen 程式
2. 撰寫 linux service 腳本並運行
3. 建立 開機程式
4. 建立 關機程式
5. 建立 取得伺服器狀態程式
6. 建立 API程式
7. 結語
安裝 screen 程式
為了要能夠在背景執行 Minecraft Server,我們需要在主機上安裝多工處理程式 screen。
首先一樣透過先前的 ssh 指令連接到 EC2 主機
ssh -i Minecraft.pem ubuntu@54.86.84.160
連到主機之後,我們需要安裝 screen 程式。
sudo apt -y update && sudo apt -y install screen
安裝完畢之後可以輸入 screen 進行測試,若有顯示以下畫面代表安裝成功。可按 enter 離開歡迎畫面。
可以發現其實目前好像沒有什麼改變,但其實你已經開啟了一個可以常駐的多工程式,所以接下來我們只要把 Minecraft Server 放在這個多工程式中執行,我們即便離開了 SSH 程式,這個程式依舊會持續執行。
按下 enter 後輸入 exit 離開。如此一來就安裝完畢了!
撰寫 linux service 腳本並運行
為了要讓每次開機都能夠自動啟動 Minecraft Server,我們可以利用 Linux 內建的 systemctl 程式來執行我們自定義的 Service。
首先我們要先建立 Service 檔案,請輸入以下的指令,輸入到最後的 EOF 記得再按一次 Enter 才會執行。
輸入以上指令後我們在 home 資料夾中建立了 minecraft.service 檔案。
cat << EOF >>minecraft.service
[Unit]
Description=Minecraft Server
After=network.target[Service]
WorkingDirectory=/home/ubuntu/minecraftUser=ubuntu
Group=ubuntuRestart=alwaysExecStart=/usr/bin/screen -DmS mc /usr/bin/java -Xmx1G -Xms500M -jar mcserver.jar noguiExecStop=/usr/bin/screen -p 0 -S mc -X stuff “say Server is shutting down in 5 seconds. Saving all maps…\015”
ExecStop=/bin/sleep 5
ExecStop=/usr/bin/screen -p 0 -S mc -X stuff “save-all\015”
ExecStop=/usr/bin/screen -p 0 -S mc -X stuff “stop\015”[Install]
WantedBy=multi-user.target
EOF
從程式碼中可以區分成幾個區塊
1. [Unit] 服務說明
2. [Service] 服務運作的路徑、權限以及執行指令等
3. [Install] 執行範圍
大家可以發現在 ExecStart 指令中,其實就是我們先前啟動 Server 的方式,這邊可以發現 java 變成絕對路徑。
接下來我們要將這個檔案放在系統路徑,請輸入以下指令
sudo cp minecraft.service /etc/systemd/system
輸入完畢之後我們需要讓系統認得這個檔案,而且啟動我們這個服務。接下來請輸入下面四個指令。
sudo systemctl daemon-reload
sudo systemctl enable minecraft.service
sudo systemctl start minecraft.service
sudo systemctl status minecraft.service
成功運行將會顯示以下畫面,可以看到 CGroup 中最下方的就是我們正在執行 Minecraft Server 的指令。
接下來試著關閉 ssh 看看,你可以發現 Minecraft Server 仍然可以連線。
恭喜你,現在已經不會因為 ssh 斷線的原因而讓伺服器關閉了!接下來我們要試著透過 API 來控制 Minecraft 伺服器的開關。
建立 開機程式
接下來我們要建立開機的 API。首先我們要先取得我們 Minecraft 主機的名稱,首先先到 EC2 的畫面,點擊主機之後尋找下方的 Instance ID 欄位,並且複製起來,這邊是`i-00fd1ca390281a21b`。
除此之外,我們還需要知道我們目前主機放在哪裡,可以點網頁最右上角的第二個三角箭頭,展開後可以看到目前橘色的地方即是我們當前的位置,請將後方的文字複製起來,這邊是`us-east-1`
接下來我們需要使用 AWS 的 Lambda 服務,這個服務是可以讓我們用很簡單的方式運行我們的程式碼,常見的語言如 Nodejs, Python。請在 AWS Console 的最上方輸入 lambda,點擊第一個項目
看到的畫面大體如下,請點擊 Create function 建立我們的開機程式。
首先先在 Function name 的文字框中輸入`StartMinecraftServer`,Runtime 的地方請選擇目前最新的`Nodejs 14.x`版本。接下來點擊 Create function 即可。
接下來我們可以點擊左方的 index.js 檔案開始編輯我們的程式碼
請將以下程式碼直接覆蓋,並將**YOUR_INSTANCE_ID**給替換成先前複製的 instance id。
接著點擊`Deploy`按鈕將程式碼上傳。
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/EC2.html#startInstances-property
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/EC2.html#stopInstances-property
const AWS = require(‘aws-sdk’);const ec2 = new AWS.EC2({ apiVersion: ‘2016–11–15’ });const INSTANCE_ID = ‘YOUR_INSTANCE_ID’;exports.handler = async (event) => {
var params = {
InstanceIds: [INSTANCE_ID],
};
const result = await ec2.startInstances(params).promise()
console.log({ result });
while (true) {
const result = await ec2.describeInstances(params).promise()
const instance = result.Reservations[0].Instances[0]
console.log({ instance })
if (instance.State.Name === ‘running’) {
console.log(‘Server is running.’);const { Reservations: [{ Instances: [{ PublicIpAddress, InstanceType }] }] } = result;
console.log({ PublicIpAddress, InstanceType });
const response = {
statusCode: 200,
body: JSON.stringify({
PublicIpAddress,
InstanceType,
}),
};
return response;
}
await new Promise(r => setTimeout(r, 1000));
}
};
程式碼首先使用 ec2.describeInstances 函數,就是取得 Instance 的狀態,接著如果主機沒有在執行,我們就嘗試執行它,直到主機已經開啟為止。
因為這個程式需要等待主機完整開機,所以可能會花較長的時間。Lambda 函數預設值如果超過3秒程式將會自動結束,所以我們需要將他的時間拉長至1分鐘。這邊我們要點擊 Configuration -> General configuration,點擊 Edit 調整 Timeout 的秒數。
將時間調整成`1`min,以及`0`sec,最後按下 Save 按鈕即可。
除此之外因為預設的 Lambda 是沒有權限去控制 EC2 主機的,所以我們需要去修改他的權限。首先要先點擊 Configuration -> Permissions,接著點擊 StartMinecraftServer-role 開始修改我們的權限
接下來會進入權限修改的畫面,我們需要點擊`Add inline policy`按鈕來加入 EC2 控制的權限。
在輸入框輸入`StartInstances`並把權限打勾,這個能夠讓我們透過Lambda去打開 EC2 Instance。
在輸入框輸入`StopInstances`並把權限打勾,這個能夠讓我們透過Lambda去關閉 EC2 Instance。
在輸入框輸入`DescribeInstances`並把權限打勾,這個能夠讓我們透過Lambda去檢查 EC2 Instance的狀態。
接下來展開Resources 後點擊 Add ARN,需要輸入先前複製起來的 Region 以及 Instance id,接著按下 Add。
再按右下角的 Review policy 按鈕,檢查一下應該是會選擇了 EC2 的 List 以及 Write 的Permission,接下來點擊 Create policy 即可。
建立 關機程式
接著我們要建立關機的 API,一樣透過上方搜尋列輸入 Lambda 進入 Lambda 頁面。
點擊 Create function 按鈕開始建立。
Function name 我們輸入 StopMinecraftServer,Runtime 一樣選擇 Nodejs14.x。但這邊要特別注意 Permissions,因為我們可以使用前一個開機 Lambda 的權限,所以先點擊 Pemissions 把它展開,選擇`Use an existing role`,在 Existing role 的地方選擇剛剛建立的 Role,名稱為 service-role/StartMinecraftServer 開頭。接下來就點擊 Create function 建立
建立完畢之後一樣點擊`index.js`輸入程式碼如下,並將**YOUR_INSTANCE_ID**給替換成先前複製的 instance id:
const AWS = require(‘aws-sdk’);
const ec2 = new AWS.EC2({ apiVersion: ‘2016–11–15’ });const INSTANCE_ID = ‘YOUR_INSTANCE_ID’;
exports.handler = async (event) => {
var params = {
InstanceIds: [INSTANCE_ID],
};
await ec2.stopInstances(params).promise()
console.log(‘Minecraft Server closed’);
const response = {
statusCode: 200,
body: JSON.stringify(‘Server closed’),
};
return response;
};
這次的程式就簡單許多,透過 ec2.stopInstances 函數將我們的主機關閉。
建立 取得伺服器狀態程式
我們一樣回到 Lambda 的畫面,點擊 Create function 建立最後一個函數。
Function name 輸入`GetMinecraftServerStatus`,Runtime 一樣選擇`Nodejs 14.x`,Permissions一樣參考前一個範例選擇名稱為 service-role/StartMinecraftServer 開頭的 Role。按下 Create function 建立函數。
建立完畢之後點擊`index.js`貼入以下程式碼,並將**YOUR_INSTANCE_ID**給替換成先前複製的 instance id:
const AWS = require(‘aws-sdk’);
const ec2 = new AWS.EC2({ apiVersion: ‘2016–11–15’ });const INSTANCE_ID = ‘YOUR_INSTANCE_ID’;exports.handler = async (event) => {
var params = {
InstanceIds: [INSTANCE_ID],
};
const result = await ec2.describeInstances(params).promise()
const instance = result.Reservations[0].Instances[0]
console.log({ instance })
if (instance.State.Name !== ‘running’) {
console.log(‘Server is not running.’);
const response = {
statusCode: 200,
body: JSON.stringify(‘Server is not running’),
};
return response;
}
const { Reservations: [{ Instances: [{ PublicIpAddress, InstanceType }] }] } = result;
console.log({ PublicIpAddress, InstanceType });
const response = {
statusCode: 200,
body: JSON.stringify({
PublicIpAddress,
InstanceType,
}),
};
return response;
};
到目前為止我們已經完成了先前設計的所有程式,包含開機、關機以及取得主機狀態。但是我們該如何執行呢?下一個步驟將會教導各位該如何透過輸入網址的方式來執行開機、關機以及取得主機狀態的動作。
建立 API程式
這個章節我們要透過 API Gateway 這個服務來整合我們的程式。API Gateway 是一個 HTTP Server,他能夠將我們輸入的網址解析成對應的動作,進而執行後續的程式。
在我們的範例之中,我們要透過3個不同的網址分別執行開機、關機以及取得主機狀態的程式。
首先在網頁最上方輸入`API Gateway`,點擊第一個項目。
接著我們要建立一個屬於 Minecraft 的 API。點擊右上角的 Create API 開始建立。
我們會看到三種 API,HTTP API、WebSocket API 以及 REST API,其 WebSocket API 是用來建立網頁即時通訊的功能,所以我們用不到。剩下 HTTP API & REST API 是我們可以使用的。以我們目前的功能來說選擇簡單的 HTTP API 即可。點擊 Build 開始建立。
接著畫面如下,Integrations 代表我們要整合的 Lambda 程式,API name 就是為這隻 API 命名。
我們先點擊 Add integration 按鈕,在選擇 Lambda。
接著會看到我們先前寫的所有 Lambda 函數,先選擇 StartMinecraftServer。
然後我們需要把剩下 Lambda 函數也加入,再點擊 Add integration 按鈕,將 StopMinecraftServer、GetMinecraftServerStatus 陸續加入。加入完畢後畫面會呈現以下。
接下來在 API name 的地方輸入`MinecraftAPI`代表這是專屬 Minecract Server 使用的 API,點擊右下方 Next 按鈕。
畫面如下,這邊可以看到 Resource path 的地方就是這三個 API 的網址,右邊對應的就是我們整合的 Lambda 程式。接著按 Next 按鈕。
第三個步驟,這邊我們使用預設值即可。點擊 Next 按鈕。
最後一個步驟,我們直接點擊右下角的 Create 即可。
目前已建立完畢。畫面中可以看到 Invoke URL,將其複製起來,接下來我們會試著將伺服器關閉,再將伺服器打開,最後再取得伺服器的資訊,然後就可以拿到 Minecraft Server 的 IP 了!
我們將剛剛的 Invoke URL 貼到瀏覽器的網址列上,再加上`/StopMinecraftServer`按下 Enter,我們會看到 Server closed 的文字,代表伺服器已經透過這個 API 來關機了。
接著我們將網址改成`/StartMinecraftServer`按下 Enter,我們會看到這樣的畫面,也就代表主機已經正常開機了。
接著我們將網址改成`/GetMinecraftServerStatus`按下 Enter,可以知道主機目前狀態是開機或是關機。
若我們將主機關機,再執行一次 GetMinecraftServerStatus,畫面則會顯示 Server is not running,代表主機目前沒有在運行。
最後我們測試 StartMinecraftServer 的網址,取得 IP,透過遊戲連線至 Minecraft Server。
恭喜您!你目前已經可以將網址傳給朋友,讓大家在想要玩的時候自行打開伺服器了,是不是很方便呢!
結語
在這次的部落格中我們學習到了如何使用Lambda搭配API Gateway來快速建立一個HTTP Server API,如此一來我們就可以自己透過修改程式碼做到不同的功能了。
在Lambda之中也運用到了強大的AWS SDK,我們可以透過這個SDK來控制所有有辦法用API控制的AWS服務。當然,文中也有提到說需要修改IAM Role來讓我們的Lambda有權限可以控制。