Ubuntu Server 24.04 LTSをインストールする

インストール用ISOファイルをダウンロード

https://jp.ubuntu.com/download

インストール用ISOファイルをUSBメモリに展開

ダウンロードしたISOファイルを展開してUSBメモリに書き込む。

自分はUSBWriterを使用。

USBメモリから起動しインストール

基本はデフォルトで設定し、以下項目だけ変更した。

  • 静的IPの設定
  • Open SSHのインストール

SSHで繋いで環境構築

sudo apt update
sudo apt upgrade
sudo reboot

Webminのインストール

curl -o setup-repos.sh https://raw.githubusercontent.com/webmin/webmin/master/setup-repos.sh
sudo sh setup-repos.sh
sudo apt install --install-recommends webmin

その他

 →Ubuntu Server 22.04 LTSをインストールする
 →Ubuntu Proを使ってみる

Windows 11で休止状態を無効にし、hiberfil.sys を削除する

デスクトップPCに休止状態の設定はいらないだろうということで、無効にする。管理者権限でコマンドプロンプトを開き下記のコマンドを実行する。

powercfg.exe -h off

無効にすると自動的にhiberfil.sysが削除される。自分の環境ではCドライブの空き容量が64GB増えた。

xsdのURLは正しいのにEclipseのエディタ上でエラーになる

xsdのURLは正しいのにDownloading external resources is disabled.と表示されてエラーになっている。

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"

下記の設定を変更することで解決した。

Mavenの設定の「Download Artifact Javadoc」にチェックを入れる。

XML (Wild Web Developer)の設定の「Download external resources like referenced DTD, XSD」にチェックを入れる。

Hibernate Searchを7.2にアップデートしたらNoSuchMethodErrorが発生した

Hibernate Searchを7.2.1にアップグレードして、テストを実施したら下記のエラーが発生した。

NoSuchMethodError: 'java.lang.Object org.jboss.logging.Logger.getMessageLogger

コンパイルエラーにはなっていないが、テスト時のログ出力でエラーとなった。

他のライブラリとの依存関係の影響で、jboss-logging 3.5系を使用するようになっていた。3.6.0.Finalを使用するようにpom.xmlに依存関係を追記したところ、エラーは解消した。

<dependency>
    <groupId>org.jboss.logging</groupId>
    <artifactId>jboss-logging</artifactId>
    <version>3.6.0.Final</version>
</dependency>

Gradleの場合も同様。

implementation 'org.jboss.logging:jboss-logging:3.6.0.Final'

Vueのアップグレードでエラー

どのバージョンで変わったか定かではないのだけど、アップグレード後エラーになって起動しなくなった。

変更点

vue.config.jsのエラーメッセージ

 ERROR  SyntaxError: Cannot use import statement outside a module
E:\Work\wtp\workspace\crawler-client\vue.config.js:1
import { defineConfig } from '@vue/cli-service'

import fromをrequireに変更した。

const { defineConfig } = require('@vue/cli-service')

babel.config.jsのエラーメッセージ

 ERROR  SyntaxError: Unexpected token 'export'
E:\Work\wtp\workspace\crawler-client\babel.config.js:1
export const plugins = {

export const pluginsをmodule.exportsに変更した。

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ]
}

PostgreSQLをダウンロードして、データベースを作成する

テスト用のユーザーと、テスト用のデータベースを作成して、実行する。

PostgreSQLのダウンロード

Zipファイルをダウンロードし、解凍する。

https://www.enterprisedb.com/download-postgresql-binaries

データベースクラスタの作成

データベース等の保存先になるフォルダを指定する。

PostgreSQLインストールフォルダ\bin\initdb.exe -D "任意フォルダ"

起動

PostgreSQLを起動する。

PostgreSQLインストールフォルダ\bin\pg_ctl.exe start -D "データベースクラスタのフォルダ" -l "任意フォルダ\ログファイル名"

以降、起動状態で作業する。

ユーザー作成

パスワード有りで、ユーザーを作成する。

PostgreSQLインストールフォルダ\bin\createuser -P testuser

データベース作成

先ほど作成したユーザーをオーナーに指定しデータベースを作成する。

PostgreSQLインストールフォルダ\bin\createdb -O testuser testdb

終了

PostgreSQLを終了する。

PostgreSQLインストールフォルダ\bin\pg_ctl.exe stop -D "データベースクラスタのフォルダ" -l "任意フォルダ\ログファイル名"

Spring BootでSessionをRedisに保存する

前回、Raspberry piで遊んだ時にTomcatによるSession Replicationを実装した。

今回はSpring BootとRedisでSession管理を外部化してSession Replicationを実装してみる。

Redisのインストール

sudo apt install redis

Redisの設定

複数サーバーからアクセス出来るようにするため、アクセス制限を削除する。

sudo vi /etc/redis/redis.conf
下記の通り修正

bind 0.0.0.0 ::0 # 一時的に使用するだけなので全てのIPからアクセスOKにしている

Spring Bootでの実装例

build.gradleの依存関係部分
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-redis-reactive'
	implementation 'org.springframework.boot:spring-boot-starter-webflux'
	implementation 'org.springframework.session:spring-session-data-redis'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'io.projectreactor:reactor-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

Sessionオブジェクトの保存にRedisを使用するようにConfigを設定する。なお、外部にデータを保存することになるので、Sessionに入れるデータはSerializableである必要がある。

import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.server.EnableRedisWebSession;

@Configuration
@EnableRedisWebSession()
public class SessionConfig {
}
Controllerの実装例
@RestController
@AllArgsConstructor
public class SessionController {

    @GetMapping("/websession")
    public Mono<SessionForm> getSession(WebSession session) {
        session.getAttributes().putIfAbsent("key", 0);
        session.getAttributes().putIfAbsent("note", "Nothing!");

        var sessionForm = new SessionForm();
        sessionForm.setKey((Integer) session.getAttributes().get("key"));
        sessionForm.setNote((String) session.getAttributes().get("note"));

        return Mono.just(sessionForm);
    }

    @GetMapping("/websession/test")
    public Mono<SessionForm> testWebSessionByParam(@RequestParam(value = "key") Integer key,
            @RequestParam(value = "note") String note, WebSession session) {
        session.getAttributes().put("key", key);
        session.getAttributes().put("note", note);

        var sessionForm = new SessionForm();
        sessionForm.setKey((Integer) session.getAttributes().get("key"));
        sessionForm.setNote((String) session.getAttributes().get("note"));

        return Mono.just(sessionForm);
    }
}

完全なコードはこちら。
https://github.com/hide6644/spring-session

動作確認

下記URLにアクセスするとパラメーターがSessionに保存される。
localhost:8080/websession/test?key=222&note=helloworld

下記のURLにアクセスすると、先ほど保存した文字が表示される。
localhost:8080/websession

{“key”:222,”note”:”helloworld”}

SessionをRedisに保存しているので、Spring Bootアプリをスケールアウトしても、それぞれのアプリでSessionが共有されていることが確認できると思う。

$ redis-cli
127.0.0.1:6379> keys *
1) "spring:session:sessions:セッションID"
127.0.0.1:6379> exit

AWSサーバーにエンドポイントで接続してみる

前回、IPv6の対応したついでにEC2のパブリックIPv4アドレスを無効にしたため、EC2のサーバーにSSHでアクセス出来なくなっている。IPv6で許可すれば良いのだが、そんなに頻繁に使用するものでもないので、エンドポイントを作成してプライベートIPに直接アクセスするように変更する。

セキュリティグループ

エンドポイント用のセキュリティグループ、EC2サーバー用のセキュリティグループを作成する。

エンドポイント

自分のVPC、先ほど作成したセキュリティグループ、接続したいEC2サーバーのあるサブネットを選択してエンドポイントを作成する。

接続

接続したいEC2インスタンスを選択し、下記の通り接続タイプを選択する。

AWSのサーバーをIPv6に対応する

最初に

これは個人用途で使用しているサーバーを、試しにIPv6に対応してみたときの備忘録になります。同じように作業するときは自己責任でお願いします。

今回IPv6に対応する箇所は、
VPC
EC2
ELB
Route 53

VPCの設定

VPCを選択して、アクションからCIDR の編集をクリックする。

新しい IPv6 CIDR を追加をクリックする。

Amazon提供のIPv6 CIDRブロックを選択して、CIDRを選択をクリックする。

サブネットを選択して、アクションからIPv6 CIDR の編集をクリックする。

IPv6 CIDR を追加をクリックして、サブネットの CIDR ブロックを設定する。

サブネットの設定を編集をクリックして、IPv4の自動割り当てを無効にして、IPv6の方を有効にする。

ルートテーブルのルート編集をクリックして、「::/0」をルートに追加する。

EC2の設定

起動済みのEC2をIPv6に変更する場合は、EC2のネットワークの設定から

新しいIPv6アドレスの自動割り当てを設定する。

ついでにパブリックIPの自動割り当てを解除する。

ELBの設定

ロードバランサを選択し、アクションからIP アドレスタイプの編集をクリックする。

Dualstackを選択し保存する。

特にここでは書かないが、セキュリティグループを設定しているなら、そちらもIPv6に対応したルールを追加する。

Route 53の設定

レコードタイプAAAAを追加する。

確認

nslookupコマンドで確認すると、IPv4、IPv6両方設定されている。

今まで通りページが表示されることを確認する。

Raspberry pi 2 Clusteringで遊ぶ

発売当時くらいに買ったRaspberry pi 2を一通り遊んだ後、放置して使っていなかったので、またちょっと遊んでみる。

ゴール

Raspberry piが手元に7台あるので、Load Balancer、 Reverse Proxy、Application Server、DB Serverで冗長構成を作成してみる。

Raspberry pi 2はメインメモリが2GB、CPUも32bitと今の基準からすると非力なので、なるべく軽い構成にする。Kubernetesとか使いたかったが、スペック的に無理だった。

Load Balancer: Nginx
Reverse Proxy: Nginx ※無くても良い
Application Server: Apache Tomcat 10 (Java 21)
DB Server: MariaDB

Ubuntu 22のインストール

Raspberry Pi Imagerを使ってUbuntuをSDカードにインストールする。

OSでOther general-purpose OS > Ubuntu > Ubuntu Server 22.04.4 LTS (32bit)を選択する。

初期設定

適当に選んだRaspberry Piで起動し、共通の設定を行ってから、SDカードのイメージを取り、他のSDカードにコピーしていく。

sudo apt update
sudo apt upgrade
sudo apt install net-tools


ローカルで使うだけなので、SSH接続でパスワード認証出来るようにしてしまう。

sudo vi /etc/ssh/sshd_config.d/60-cloudimg-settings.conf
下記の通り修正

PasswordAuthentication yes


静的なIPを割り当てる。
イメージを他の機器にコピーするときはIPを変更する。

sudo vi /etc/netplan/50-cloud-init.yaml
下記の通り修正

network:
    ethernets:
        eth0:
            dhcp4: false
            dhcp6: false
            addresses: [ローカルIP/24] #設定したIPは機器ごとに後で変更すること
            routes:
                - to: default
                  via: ゲートウェイIP #無いならroutesの設定いらない
            nameservers:
                addresses: [8.8.8.8, 8.8.4.4]
    version: 2


ホスト名を変更可能にする。

sudo netplan try
sudo vi /etc/cloud/cloud.cfg
下記の通り修正

preserve_hostname: true


ホスト名を設定する。
イメージを他の機器にコピーするときはホスト名を変更する。

sudo hostnamectl set-hostname rp1 #設定したホスト名は機器ごとに後で変更すること

Load Balancer

初期設定したイメージから作業を始める。

nginxをインストールする。

sudo apt install nginx
sudo systemctl start nginx
sudo systemctl enable nginx
sudo vi /etc/nginx/nginx.conf
下記の通り修正

worker_connections 16;


Web Serverとしては使用しないのでdefaultの設定は読み込まないようにし、Load Balancer用の設定ファイルを作成する。

sudo rm /etc/nginx/sites-enabled/default
sudo vi /etc/nginx/conf.d/loadbalancer.conf
下記の通り新規作成

upstream backend {
    #least_conn;
    #ip_hash; #設定しても良いが、ラウンドロビンされていることを直ぐに確認したいので今回はなし
    server Reverse Proxy Server No.1のIP:80;
    server Reverse Proxy Server No.2のIP:80;
}

server {
    listen 80 default_server;
    server_name _;

    location / {
        proxy_pass http://backend;
    }
}


設定ファイルに誤りがないことを確認し、nginxを再起動する。

sudo nginx -t
sudo systemctl restart nginx

Reverse Proxy

初期設定したイメージから作業を始める。
Reverse Proxy Serverは2台作成する。

nginxをインストールする。

sudo apt install nginx
sudo systemctl start nginx
sudo systemctl enable nginx
sudo vi /etc/nginx/nginx.conf
下記の通り修正

worker_connections 16;


Web Serverとしては使用しないのでdefaultの設定は読み込まれないようにし、Reverse Proxy用の設定ファイルを作成する。

sudo rm /etc/nginx/sites-enabled/default
sudo vi /etc/nginx/conf.d/proxy.conf
下記の通り新規作成

server {
    listen 80 default_server;
    server_name _;

    location / {
        proxy_pass http://Application Server1のIP:8080;
    }
}


もう1台のReverse Proxy Serverも同様に設定する。

Application Serverも2台構成にする予定のため、
proxy_pass http://Application Server1のIP:8080;のIPはそれぞれ別のIPになる。

設定ファイルに誤りがないことを確認し、nginxを再起動する。

sudo nginx -t
sudo systemctl restart nginx

Application Server

Java 21のインストール
Tomcat 10のインストール

Tomcat 10はインストールパッケージがなかったため、Apache Tomcatのページからダウンロードして解凍する。実行ユーザー、シンボリックリンクも作っておく。

sudo apt install openjdk-21-jdk

cd /opt
sudo tar xvfz /mnt/nfs/apache-tomcat-10.1.20.tar.gz
sudo ln -s /opt/apache-tomcat-10.1.20 tomcat
sudo useradd -m -U -d /opt/tomcat -s /bin/false tomcat
sudo chown -R tomcat: /opt/apache-tomcat-10.1.20


起動用のスクリプトを作成する。

sudo vi /usr/lib/systemd/system/tomcat.service
下記の通り新規作成

#
# Systemd unit file for Apache Tomcat
#
[Unit]
Description=Apache Tomcat 10 Web Application Server
After=network.target
[Service]
# Configuration
Environment="JAVA_OPTS=-Djava.awt.headless=true"
Environment="CATALINA_BASE=/opt/tomcat"
Environment="CATALINA_HOME=/opt/tomcat"
Environment="CATALINA_OPTS=-Xmx1024m -server"
Environment="CATALINA_PID=/opt/tomcat/temp/tomcat.pid"
# Lifecycle
Type=forking
ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh
Restart=always
PIDFile=/opt/tomcat/temp/tomcat.pid
# Logging
SyslogIdentifier=tomcat
# Security
User=tomcat
Group=tomcat
[Install]
WantedBy=multi-user.target


自動起動は設定しない。

sudo systemctl daemon-reload
sudo systemctl start tomcat


もう1台のApplication Serverも同様に設定する。

起動確認が終わったら一度Tomcatを停止して、レプリケーションの設定をserver.xmlに追記する。

今回は簡単に確認するため、SimpleTcpClusterを使用している。別の機会にSpring Sessionを使用した方法を試してみたい。

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
         channelSendOptions="6">
  <Manager className="org.apache.catalina.ha.session.BackupManager"
           expireSessionsOnShutdown="false"
           notifyListenersOnReplication="true"
           mapSendOptions="6"/>
  <Channel className="org.apache.catalina.tribes.group.GroupChannel">
    <Membership className="org.apache.catalina.tribes.membership.McastService"
                address="228.0.0.4"
                port="45564"
                frequency="500"
                dropTime="3000"/>
    <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
              address="auto"
              port="5000"
              selectorTimeout="100"
              maxThreads="6"/>

    <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
      <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
    </Sender>
    <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
    <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>
    <Interceptor className="org.apache.catalina.tribes.group.interceptors.ThroughputInterceptor"/>
  </Channel>

  <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""/>

  <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>


もう1台のApplication Serverも同様に設定する。

DB Server

まずは3台に対してMariaDBをインストールして初期設定を実行する。

sudo apt install mariadb-server
sudo systemctl stop mariadb
sudo mkdir /mnt/ssd/mariadb


データの保存先を変更し、リモートアクセス可能にする。

sudo vi /etc/mysql/mariadb.conf.d/50-server.cnf
下記の通り修正

datadir                 = /mnt/ssd/mariadb
#bind-address            = 127.0.0.1


MariaDBが外部フォルダにアクセス出来るようにApplication Armorのセキュリティ設定を変更する。

sudo vi /etc/apparmor.d/tunables/alias
下記の通り修正

alias /var/lib/mysql/ -> /mnt/ssd/mariadb/,


Application Armorを再起動し、MariaDBのデータフォルダをコピーする。

sudo systemctl restart apparmor
sudo rsync -avuz /var/lib/mysql/ /mnt/ssd/mariadb


MariaDBを起動し、初期設定する。

sudo systemctl start mariadb
sudo mysql_secure_installation
Enter current password for root (enter for none):
Switch to unix_socket authentication [Y/n] n
Change the root password? [Y/n] 
New password:
Re-enter new password:
Remove anonymous users? [Y/n] y
Disallow root login remotely? [Y/n] n
Remove test database and access to it? [Y/n] y
Reload privilege tables now? [Y/n] y

mysql -u root -p
grant all privileges on *.* to root@"%" identified by 'password' with grant option;
exit;

sudo systemctl restart mariadb


接続確認を行ったら、一度停止する。

MariaDBをインストールすると自動的にGalera Clusterがインストールされているはず。

自動起動を無効にし、バイナリロギングを有効にする。

sudo systemctl disable mariadb
sudo mkdir -p /mnt/ssd/log/mariadb
sudo chown -R mysql: /mnt/ssd/log/mariadb
sudo vi /etc/mysql/mariadb.conf.d/50-server.cnf
下記の通り修正

server-id               = 1 # サーバー毎に変更
log_bin                 = /mnt/ssd/log/mariadb/mariadb-bin.log


Galera Clusterの設定を行う。

sudo vi /etc/mysql/mariadb.conf.d/60-galera.cnf
下記の通り修正

[galera]
# Mandatory settings
wsrep_on                 = ON
wsrep_cluster_name       = "MariaDB Galera Cluster"
wsrep_cluster_address    = gcomm://サーバーIP1,サーバーIP2,サーバーIP3
binlog_format            = row
default_storage_engine   = InnoDB
innodb_autoinc_lock_mode = 2
wsrep_provider           = /usr/lib/galera/libgalera_smm.so

# Allow server to accept connections on all interfaces.
bind-address = 0.0.0.0


最初の1台は下記コマンドで実行する。以降は通常の起動コマンドで起動する。

sudo galera_new_cluster
※再起動時の注意

データが保存されているフォルダにgrastate.datというファイルがあるので、safe_to_bootstrap: 1となっているサーバーからgalera_new_clusterで起動する。

sudo less /mnt/ssd/mariadb/grastate.dat

# GALERA saved state
version: 2.1
uuid:    xxx
seqno:   -1
safe_to_bootstrap: 1


それ以外だと、エラー(WSREP: It may not be safe to bootstrap the cluster from this node. It was not the last one to leave the cluster and may not contain all the updates. To force cluster bootstrap with this node, edit the grastate.dat)となって起動できない。

動作確認

Javaアプリを作りクラスタリングされているか確認する。