我常用的一個場景是一台Linux底下有多個Spring Application,可以透過Linux systemd控制他們,然後使用Nginx的反向代理產生唯一對外窗口,讓外界透過Nginx與Spring Application溝通。以下教學在Debian與Ubuntu都是可以使用的。CentOS可能也可以,但自從RedHat宣布準備停止支援後我就沒再用CentOS了,所以沒測試過。

Spring Boot設置

Spring Application可以用gradle bootJar產生jar檔案執行,或是直接對專案原始碼執行gradle bootRun執行。沒安裝Gradle的時候通常專案內會有gradlew可以免安裝使用,可以在專案根目錄使用./gradlew bootRun

安裝Java、JDK

在Linux上還沒安裝Java的話,這邊選擇最新的LTS版比較穩定,也就是Java 17。

# 包含JRE與JDK的Headless版(推薦)
sudo apt install openjdk-17-jdk-headless
# 包含JRE與JDK的完整版
sudo apt install openjdk-17-jdk
# 只有Java Runtime環境的版本,沒有javac等編譯指令
sudo apt install openjdk-17-jre-headless

新增使用者

我習慣增加一個不可登入的使用者,名為springboot,專門給Spring Boot使用。如果更嚴謹一點,也可以為每個不同的Spring Boot都新增一個使用者。

sudo useradd -r -s /bin/false springboot
  • 參數 -r:建立系統使用者
  • 參數 -s:指定Login Shell為不能登入

如果要讓springbooot帳號可以執行gradle指令,例如./gradlew bootRun之類的方法,就不要在新增使用者時增加-r參數,因為執行gradlew時需要一個家目錄給他存放gradle相關的檔案。如果像是以下範例那樣直接執行jar檔案就沒這個差別,可以使用-r避免新增帳號家目錄。

建立Service設置

建立一個檔案「/etc/systemd/system/spring-app.service」,其中檔案名稱「spring-app」可以替換成你的服務的名稱。檔案內的「ExecStart」設置可以依照個人需求調整,也可以改成啟動一個shell腳本,將詳細的啟動指令寫在腳本內。

[Unit]
Description=Klab.tw DEMO
After=syslog.target

[Service]
Restart=always
RestartSec=3
User=springboot
ExecStart=/usr/bin/java -Xmx1024m -Xms1024m -Dserver.port=8080 -jar /home/springboot/app.jar --spring.profiles.active=prod

[Install]
WantedBy=multi-user.target

上面把各種指令都寫上ExecStart看了有點亂,也可以改用以下寫法。

[Unit]
Description=Klab.tw DEMO
After=syslog.target

[Service]
Restart=always
RestartSec=3
User=springboot
WorkingDirectory=/home/springboot
Environment="SPRING_PROFILES_ACTIVE=prod"
Environment="JAVA_OPTS=-Xmx1024m -Xms1024m -Dserver.port=8080"
ExecStart=/usr/bin/java $JAVA_OPTS -jar app.jar

[Install]
WantedBy=multi-user.target

用了WorkingDirectory指定了工作資料夾,Environment設定環境變數,這樣ExecStart就可以簡化許多,讀起來也更容易囉。

Linux Systemd

systemd由systemctl控制,需要最高權限,所以要使用sudo,或是直接轉成root身份喔。

讀取Service設置

每次新增或是修改systemd內的.service檔案後,要呼叫systemctl重新讀取設置。

sudo systemctl daemon-reload

控制方式

sudo systemctl start spring-app    # 啟動服務
sudo systemctl stop spring-app     # 停止服務
sudo systemctl restart spring-app  # 重新啟動服務
sudo systemctl enable spring-app   # 開機後自動啟動
sudo systemctl disable spring-app  # 開機後不要自動啟動

檢查執行狀況

sudo systemctl status spring-app      # 檢查服務狀態,可以順便看見一些服務輸出的訊息
sudo systemctl is-active spring-app   # 檢查是否正在啟動著,如果是會回傳active
sudo systemctl is-enabled spring-app  # 檢查是否在開機後會自動啟動,如果是會回傳enabled
sudo systemctl is-failed  spring-app  # 檢查是否啟動失敗,如果不是會回傳active
sudo journalctl -u spring-app         # 觀看Spring Application輸出的訊息,包含Stdout與Stderr等。

最後一行提到的journalctl,參數-u spring-app是篩選只要觀看spring-app的輸出。的還可以加上-f參數,會顯示最後數行訊息,而且會在有新訊息時自動更新。還可以用-r參數將訊息反向排序輸出,但是-r不可與-f一同使用。

Nginx設置

安裝方式

sudo systemctl disable --now apache2  # 避免已經有apache會衝突,先將其關閉
sudo apt install nginx -y

反向代理Spring服務

新增一個設定檔案放在「/etc/nginx/conf.d/spring-app.conf」,一樣檔案名稱「spring-app」的部分可以改成自己需要的名字。

server {
  listen 80;
  server_name klab.tw;  # Domain name 依需求自行修改

  # 一些靜態資源可以不經過反向代理,由Nginx直接返回
  location ~ ^/(|css|ext|img|js)/ {
    root /opt/Spring-App/src/main/resources/static/;
  }

  # 需要反向代理的部分
  location / {
    proxy_pass http://localhost:8080;  # 依需求自行修改
    proxy_set_header Host $http_host;  # 這些是Nginx傳給Spring的HTTP Header
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
  }
}

檢查Nginx設置是否正確

sudo nginx -t

每次更改Nginx內的設定檔後,先不要急著生效,可以先用nginx -t來檢查是否有錯誤。

呼叫Nginx重新讀取設置

sudo systemctl reload nginx

Nginx多一個reload指令可以使用,可以不重啟Nginx就讓新的設定生效,是個方便的功能。