もなかアイスの試食品

「とりあえずやってみたい」そんな気持ちが先走りすぎて挫折が多い私のメモ書きみたいなものです.

Vagrantを使ったPostgreSQL9.6非同期レプリケーションの環境構築

はじめに

とあるリリース前のサービスの負荷試験をやったときに、将来的にDBを複数台使用するとして、Webアプリケーションをどのように変更したら良いか分からなかった。

WebアプリケーションはSpringBootを使用しており、アプリケーションの機能追加で、DBの負荷分散に対応するようにした。

その時の記事はこれ↓

monakaice88.hatenablog.com

そんな経緯があったので、アプリケーションを改造する前に、AWSでリードレプリカを使用するのを想定して、PostgreSQLで非同期レプリケーション環境が開発環境に欲しかった。

なので、今回はPostgreSQLで非同期レプリケーション環境をVagrantで構築したお話。

目次


参考サイト

以下のサイトを参考にした。

qiita.com


環境

  • CentOS 7
  • PostgreSQL 9.6
    • マスタ側IP:192.168.35.10
    • スレーブ側IP:192.168.35.11


マスタ側の構築スクリプトの作成

マスタ側のプロビジョニングスクリプトを作成する。

マスタ側のプロビジョニングスクリプトは以下の感じ

ファイル名は「create_db_master_server.sh」で保存

# いつものやーつ
sudo yum update -y
sudo yum install vim wget -y

# 時刻を日本にする
sudo cp -p /usr/share/zoneinfo/Japan /etc/localtime

# 時刻を自動で調整するようにする
cp /etc/chrony.conf /etc/chrony.conf_preprovision
sudo sed -i -e "s/server 0.centos.pool.ntp.org iburst/server ntp.nict.jp/g" /etc/chrony.conf
sudo sed -i -e "s/server 1.centos.pool.ntp.org iburst/server ntp1.jst.mfeed.ad.jp/g" /etc/chrony.conf
sudo sed -i -e "s/server 2.centos.pool.ntp.org iburst/server ntp2.jst.mfeed.ad.jp/g" /etc/chrony.conf
sudo sed -i -e "s/server 3.centos.pool.ntp.org iburst/server ntp3.jst.mfeed.ad.jp/g" /etc/chrony.conf
sudo chronyc -a makestep

# PostgreSQLのインストール
sudo rpm -U https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-centos96-9.6-3.noarch.rpm
sudo yum install -y postgresql96 postgresql96-server

# postgresユーザで「initdb」を実行するため、rootユーザになる
sudo su -
sudo su postgres -c "/usr/pgsql-9.6/bin/initdb /var/lib/pgsql/9.6/data/"

PG_DIR="/var/lib/pgsql/9.6/data/"

sudo su postgres -c "cp ${PG_DIR}postgresql.conf ${PG_DIR}postgresql.conf_preprovision"
# 接続受付IPを変更
sudo sed -i -e "s/#listen_addresses = 'localhost'/listen_addresses = '*'/g" ${PG_DIR}postgresql.conf
# 各ログの設定を変更
# Webアプリの接続先が読み取り用と更新用で分かれるか確認のため、
# 「log_connections」、「log_disconnections 」を有効にする
sudo sed -i -e "s/log_filename = 'postgresql-%a.log'/log_filename = 'postgresql-%m%d.log'/g" ${PG_DIR}postgresql.conf
sudo sed -i -e "s/#log_checkpoints = off/log_checkpoints = on/g" ${PG_DIR}postgresql.conf
sudo sed -i -e "s/#log_connections = off/log_connections = on/g" ${PG_DIR}postgresql.conf
sudo sed -i -e "s/#log_disconnections = off/log_disconnections = on/g" ${PG_DIR}postgresql.conf
sudo sed -i -e "s/#log_lock_waits = off/log_lock_waits = on/g" ${PG_DIR}postgresql.conf
sudo sed -i -e "s/log_timezone = 'UTC'/log_timezone = 'Japan'/g" ${PG_DIR}postgresql.conf

# レプリケーション設定の変更
# https://qiita.com/U_ikki/items/e117acad0413546d6923
sudo sed -i -e "s/#checkpoint_timeout = 5min/checkpoint_timeout = 15min/g" ${PG_DIR}postgresql.conf
sudo sed -i -e "s/#hot_standby = off/hot_standby = on/g" ${PG_DIR}postgresql.conf
sudo sed -i -e "s/#wal_level = minimal/wal_level = replica/g" ${PG_DIR}postgresql.conf
sudo sed -i -e "s/#max_wal_senders = 0/max_wal_senders = 3/g" ${PG_DIR}postgresql.conf

# 接続許可の設定を変更
sudo su postgres -c "cp ${PG_DIR}pg_hba.conf ${PG_DIR}pg_hba.conf_preprovision"
sudo sed -i -e "87i host    replication     postgres        192.168.35.0/24         trust" ${PG_DIR}pg_hba.conf
sudo sed -i -e "88i host    all             all             192.168.35.0/24         md5" ${PG_DIR}pg_hba.conf

# 参照先DNSサーバの変更(今回あまり関係なし)
sudo cp /etc/NetworkManager/NetworkManager.conf /etc/NetworkManager/NetworkManager.conf_preprovision
sudo sed -i -e "26i dns=none" /etc/NetworkManager/NetworkManager.conf
sudo systemctl restart NetworkManager
sudo sed -i -r "s/nameserver [\.0-9]+/nameserver 192.168.35.20/g" /etc/resolv.conf

# 自動起動ON&PostgreSQL起動
sudo systemctl enable postgresql-9.6
sudo systemctl start postgresql-9.6

# アカウントのパスワードを変更するSQLを実行
sudo psql -U postgres sample_db < /vagrant/db/set_password_to_db.sql


スレーブ側の構築スクリプトの作成

スレーブ側のプロビジョニングスクリプトを作成する。

スレーブ側のプロビジョニングスクリプトは以下の感じ

ファイル名は「create_db_slave_server.sh」で保存する。

# いつものやーつ
sudo yum update -y
sudo yum install vim wget -y

# 時刻を日本にする
sudo cp -p /usr/share/zoneinfo/Japan /etc/localtime

# 時刻を自動で調整するようにする
cp /etc/chrony.conf /etc/chrony.conf_preprovision
sudo sed -i -e "s/server 0.centos.pool.ntp.org iburst/server ntp.nict.jp/g" /etc/chrony.conf
sudo sed -i -e "s/server 1.centos.pool.ntp.org iburst/server ntp1.jst.mfeed.ad.jp/g" /etc/chrony.conf
sudo sed -i -e "s/server 2.centos.pool.ntp.org iburst/server ntp2.jst.mfeed.ad.jp/g" /etc/chrony.conf
sudo sed -i -e "s/server 3.centos.pool.ntp.org iburst/server ntp3.jst.mfeed.ad.jp/g" /etc/chrony.conf
sudo chronyc -a makestep


# PostgreSQLのインストール
sudo rpm -U https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-centos96-9.6-3.noarch.rpm
sudo yum install -y postgresql96 postgresql96-server

# postgresユーザで「pg_basebackup」を実行するため、rootユーザになる
sudo su -
sudo su postgres -c "pg_basebackup -h 192.168.35.10 -D /var/lib/pgsql/9.6/data/ -X stream --progress -U postgres -R"

# 参照先DNSサーバの変更(今回あまり関係なし)
sudo cp /etc/NetworkManager/NetworkManager.conf /etc/NetworkManager/NetworkManager.conf_preprovision
sudo sed -i -e "26i dns=none" /etc/NetworkManager/NetworkManager.conf
sudo systemctl restart NetworkManager
sudo sed -i -r "s/nameserver [\.0-9]+/nameserver 192.168.35.20/g" /etc/resolv.conf

# 自動起動ON&PostgreSQL起動
sudo systemctl enable postgresql-9.6
sudo systemctl start postgresql-9.6


仮想マシンの設定ファイルの作成

仮想マシンのCPU数・メモリサイズを定義する設定ファイルを用意する。

設定ファイル名は「vmconf.yml」で保存

# 仮想マシンのスペック設定
vm:
  # DBサーバ(マスター・スレーブ共通)
  db:
    cpus: 2
    memory: 1024

マスター・スレーブ両方同じ設定を使用

今回はCPUコア数2個、メモリサイズ1GBに設定


Vagrantファイルの作成

# -*- mode: ruby -*-
# vi: set ft=ruby :

require 'yaml'
settings = YAML.load_file 'vmconf.yml'

Vagrant.configure("2") do |config|

  config.vm.box = "centos/7"

  # postgresql マスターサーバ
  config.vm.define :db_master do |define|
    define.vm.hostname = "db-master"
    define.vm.provision "db-master", type: "shell", :path => "create_db_master_server.sh", :privileged => false
    define.vm.network "forwarded_port", guest: 5432, host: 5432
    define.vm.network "private_network", ip: "192.168.35.10"
    define.vm.provider "virtualbox" do |provider|
      provider.name = "db-master"
      provider.cpus = settings['vm']['db']['cpus']
      provider.memory = settings['vm']['db']['memory']
    end
  end

  # postgresql スレーブサーバ
  config.vm.define :db_slave do |define|
    define.vm.hostname = "db-slave"
    define.vm.provision "db-slave", type: "shell", :path => "create_db_slave_server.sh", :privileged => false
    define.vm.network "forwarded_port", guest: 5432, host: 55432
    define.vm.network "private_network", ip: "192.168.35.11"
    define.vm.provider "virtualbox" do |provider|
      provider.name = "db-slave"
      provider.cpus = settings['vm']['db']['cpus']
      provider.memory = settings['vm']['db']['memory']
    end
  end

end


おわりに

レプリケーション構成のPostgreSQLを開発環境に構築することができ、この環境をもとにアプリケーションの改造が出来た。

AWSの運用環境を想定して、DNSサーバも構築したので、このことについてもいつか書きたい・・・

SpringBootで接続先のデータベースを動的に切り替える

はじめに

とあるサービスを作成することになり、想定する利用人数をもとに、負荷分散について調べていた。

現状大丈夫そうだなと思っていても、今後利用人数が増えることを考えると、DBの負荷分散はどうしたら良いのか分からなかった。

負荷分散について考える前までは、レプリケーションやら、AWSのリードレプリカの概要・説明が出てくるけど、アプリケーションの変更が必要なのか良く分からなかった。

調べてみると、やっぱりアプリケーションの改造をやるか、PostgreSQLの場合はpgpoolのようなミドルウェアが必要っぽい。

AWS上の環境をあまり増やしたくないので、SpringBootで作成したアプリケーションに、DBの接続先を切り替える機能を作ることにした。


目次


参考サイト

以下のサイトを参考にした。

fedulov.website

qiita.com

javaworld.helpfulness.jp

m-namiki.hatenablog.jp


環境

AWS上で想定している将来の環境は以下の感じ。

f:id:monakaice88:20180728164600p:plain

ということで、開発環境にDNSサーバ・レプリケーションを構成済みのPostgreSQL2台を構築した。

DBのロードバランスは、DNSサーバにお任せすることにした。


作業の概要

いろいろやることがあったので、ざっくり以下にまとめ

  1. コンテキストを保持するクラスを作成
  2. コンテキストより、使用するBean名を返すクラスの作成
  3. 接続先を指定するアノテーションを作成
  4. コンテキストを切り替えるAspectを作成
  5. 読み取り用と更新用とそれぞれのDB接続設定を追加
  6. Beanを作成
  7. コントローラにアノテーションを付与

サービスにアノテーションを付与するようにしたかったけれども、サービスに付与するとテストが通らなった。

コントローラに付与すると問題なく動作し、また既に色々処理が出来てしまっていたので、今回はコントローラにアノテーションを付与ようにした。

(コントローラのメソッドの中で、「読み込み用」のサービスメソッドの次に「更新用」サービスメソッドを呼んでいるのが原因。"このコネクションじゃDBの更新は出来ないよ"みたいなことを言われていた気がする。ビジネスロジックが1つのサービスのメソッドでまとまっていれば、サービスにアノテーション付与出来たと思う)


1. コンテキストを保持するクラスを作成

まずは、Enumを作っておく。

public enum DataSourceType {
    ReadOnly,
    Updatable,
}

接続先の設定を保持するコンテキストクラスを以下のように作成

public class DbContextHolder {

    private static ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>();

    public static void setDataSourceType(DataSourceType type) {
        contextHolder.set(type);
    }

    public static DataSourceType getDataSourceType() {
        return contextHolder.get();
    }

    public static void clear() {
        contextHolder.remove();
    }

}


2. コンテキストより、使用するBean名を返すクラスの作成

AbstractRoutingDataSourceを継承したクラスを作成。(定数文字列:READ_ONLY_DATA_SOURCE_NAME、UPDATABLE_DATA_SOURCE_NAMEは別のクラスで定義している)

public class RoutingDataSourceResolver extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        if (DbContextHolder.getDataSourceType() == null) {
            return null;
        }

        switch (DbContextHolder.getDataSourceType()) {
            case ReadOnly:
                return READ_ONLY_DATA_SOURCE_NAME;
            case Updatable:
                return UPDATABLE_DATA_SOURCE_NAME;
            default:
                throw new RuntimeException("unknown datasource");
        }
    }
}


3. 接続先を指定するアノテーションを作成

Controllerクラスのメソッドに付与し、「読み込み用」か「更新用」どちらに接続するかを設定するアノテーションを作成

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {

    DataSourceType value();

}


4. コンテキストを切り替えるAspectを作成

先程のアノテーションをもとに、コンテキストを設定するアスペクトを作成する。

今回はコントローラのメソッド単位で、接続先を切り替えるので、「@Controller」、「@RestController」が付いていないクラスから呼ばれると例外を投げるようにした。

@Aspect
@Component
public class SwitchingDataSourceAspect {

    @Around("@annotation(com.example.sample.aspect.DataSource)")
    public Object proceed(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            DataSourceType type = this.getDataSourceType(joinPoint);
            DbContextHolder.setDataSourceType(type);
            return joinPoint.proceed();
        } finally {
            DbContextHolder.clear();
        }
    }


    private DataSourceType getDataSourceType(JoinPoint joinPoint) throws NoSuchMethodException {
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        String methodName = signature.getMethod().getName();

        Class<?>[] parameterTypes = signature.getMethod().getParameterTypes();
        Method method = joinPoint.getTarget().getClass().getMethod(methodName, parameterTypes);

        Controller controller = joinPoint.getTarget().getClass().getAnnotation(Controller.class);
        RestController restController = joinPoint.getTarget().getClass().getAnnotation(RestController.class);
        DataSource dataSource = method.getAnnotation(DataSource.class);

        if (controller == null && restController == null) {
            throw new IllegalArgumentException("@Datasource is only can use in @Controller or @RestController.");
        }

        return dataSource.value();
    }

}


5. 読み取り用と更新用とそれぞれのDB接続設定を追加

SpringBootの設定ファイルに「readOnly」と「updatable」を追加。

spring:
  datasource:
    readOnly:
      driverClassName: org.postgresql.Driver
      url: jdbc:postgresql://read-only-db.sample.internal:5432/sample_db
      username: xxxxxxxx
      password: xxxxxxxx
      minIdlePoolSize: 3
      maximumPoolSize: 40
      idleTimeout_ms: 300000
      maxLifetime_ms: 1800000
    updatable:
      driverClassName: org.postgresql.Driver
      url: jdbc:postgresql://updatable.sample.internal:5432/sample_db
      username: xxxxxxxx
      password: xxxxxxxx
      minIdlePoolSize: 3
      maximumPoolSize: 20
      idleTimeout_ms: 300000
      maxLifetime_ms: 1800000


6. Beanを作成

設定ファイルをもとに、DataSourceを生成するBeanを作成。

DBマイグレーションでFlywayを使用している場合、更新用のBeanに「@FlywayDataSource」を付与する。これがないと起動できなかった。

RoutingDataSourceResolverを返すBean(multiDataSource)に「@Primary」を付与する。これがないと、「multiDataSource、readOnlyDataSource、updatableDataSourceどれを使うんだよ?」的なことを言われて起動できなかった。

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    public static final String READ_ONLY_DATA_SOURCE_NAME = "readOnlyDataSource";
    public static final String UPDATABLE_DATA_SOURCE_NAME = "updatableDataSource";

    @Autowired
    private Environment environment;

    @Autowired
    @Qualifier(READ_ONLY_DATA_SOURCE_NAME)
    private DataSource readableDataSource;

    @Autowired
    @Qualifier(UPDATABLE_DATA_SOURCE_NAME)
    private DataSource updatableDataSource;


    @Bean
    @Primary
    public RoutingDataSourceResolver multiDataSource() {
        RoutingDataSourceResolver resolver = new RoutingDataSourceResolver();

        // スイッチするデータソースを設定
        Map<Object, Object> dataSources = new HashMap<>();
        dataSources.put(READ_ONLY_DATA_SOURCE_NAME, readableDataSource);
        dataSources.put(UPDATABLE_DATA_SOURCE_NAME, updatableDataSource);

        resolver.setTargetDataSources(dataSources);
        resolver.setDefaultTargetDataSource(updatableDataSource);
        return resolver;
    }


    @Bean(READ_ONLY_DATA_SOURCE_NAME)
    public DataSource readOnlyDataSource() {
        String baseConfig = "spring.datasource.readOnly.%s";
        HikariConfig config = new HikariConfig();
        config.setDriverClassName(environment.getProperty(String.format(baseConfig, "driverClassName")));
        config.setJdbcUrl(environment.getProperty(String.format(baseConfig, "url")));
        config.setUsername(environment.getProperty(String.format(baseConfig, "username")));
        config.setPassword(environment.getProperty(String.format(baseConfig, "password")));
        HikariDataSource dataSource = new HikariDataSource(config);
        dataSource.setMinimumIdle(Integer.parseInt(environment.getProperty(String.format(baseConfig, "minIdlePoolSize"))));
        dataSource.setMaximumPoolSize(Integer.parseInt(environment.getProperty(String.format(baseConfig, "maximumPoolSize"))));
        dataSource.setIdleTimeout(Long.parseLong(environment.getProperty(String.format(baseConfig, "idleTimeout_ms"))));
        dataSource.setMaxLifetime(Long.parseLong(environment.getProperty(String.format(baseConfig, "maxLifetime_ms"))));
        return dataSource;
    }


    @Bean(UPDATABLE_DATA_SOURCE_NAME)
    @FlywayDataSource
    public DataSource updatableDataSource() {
        String baseConfig = "spring.datasource.updatable.%s";
        HikariConfig config = new HikariConfig();
        config.setDriverClassName(environment.getProperty(String.format(baseConfig, "driverClassName")));
        config.setJdbcUrl(environment.getProperty(String.format(baseConfig, "url")));
        config.setUsername(environment.getProperty(String.format(baseConfig, "username")));
        config.setPassword(environment.getProperty(String.format(baseConfig, "password")));
        HikariDataSource dataSource = new HikariDataSource(config);
        dataSource.setMinimumIdle(Integer.parseInt(environment.getProperty(String.format(baseConfig, "minIdlePoolSize"))));
        dataSource.setMaximumPoolSize(Integer.parseInt(environment.getProperty(String.format(baseConfig, "maximumPoolSize"))));
        dataSource.setIdleTimeout(Long.parseLong(environment.getProperty(String.format(baseConfig, "idleTimeout_ms"))));
        dataSource.setMaxLifetime(Long.parseLong(environment.getProperty(String.format(baseConfig, "maxLifetime_ms"))));
        return dataSource;
    }

}

setDefaultTargetDataSourceメソッドに「updatableDataSource」を突っ込んでいるので、@DataSourceのつけ忘れがあった場合、更新用のDBに接続してくれるはず(ちゃんと調べてない)

DataSourceにはHikariDataSourceを使用するようにした。

良くBasicDataSourceを使っているサンプルを見かけるけど、Webサーバを起動したままPostgreSQLを再起動すると、初回アクセスのときにDBコネクションエラーが発生する。

HikariDataSourceだと勝手にコネクションを再生成してくれて、初回アクセスでも問題なかった。

BasicDataSourceでも、testOnBorrowとかvalidationQueryを設定すれば、再接続してくれるっぽいけど・・・残念ながらうまくいったことがない・・・

あと、BasicDataSourceを使用して実際に負荷をかけてみたところ、上限無しにコネクションを生成し、「クライアント多すぎぃ」とDB怒られた・・・確かにコネクションの上限設定してなかったもんね。

上限の設定について一応調べた気がするけど、面倒くさくなった

ということがあったので、DataSourceにはHikariDataSourceを使用するようにした。(2回目)


7. コントローラにアノテーションを付与

接続先を切り替える処理が完成したので、あとはコントローラのメソッドに、@DataSourceを付与していく。

以下、雑なサンプルコード

@Controller
@RequestMapping("api/v1/foo")
public class FooController {

    private final FooService service;

    @Autowired
    public FooController(FooService service) {
        this.service= service;
    }

    // 読み取りのみの処理
    @DataSource(DataSourceType.ReadOnly)
    @RequestMapping(value = "", method = RequestMethod.GET)
    public ResponseEntity getFoo() {
        return ResponseEntity.ok(this.service.getFoo());
    }

    // 更新を含む処理
    @DataSource(DataSourceType.Updatable)
    @RequestMapping(value = "", method = RequestMethod.DELETE)
    public ResponseEntity deleteFoo() {
        this.service.deleteFoo()
        return ResponseEntity.ok(new MessageOnly("Foo Deleted"));
    }

}


おわりに

少々追加するクラスが多いものの、アノテーションを付与して、DBの接続先を切り替える機能を作成することができた。

SpringBootが設定を勝手にやってくれていたところが結構あり、色んなハマったポイントがあった。

利用人数が増加するアプリを作成するときは、一番最初にこの処理を作っておくべきだと思った。

そういえばDNSサーバの構築について書いてない・・・

今回、初めてCentOSDNSサーバを構築したので、それについてはまたいつか・・・

追記。DNSサーバの構築についてはコチラ

monakaice88.hatenablog.com

Vagrantでロードバランサ+WEBサーバ×2+DBを構築

はじめに

Webアプリやら常駐サービスを開発するときに、いつも新しく仮想サーバを作成するのだけれども、容量がでかいし、構築に時間がかかる。

「いつか使うかも」って思って、全然消さなかったりする

一緒に開発するからといって、仮想サーバを渡しても、IPアドレスが変わったりして、普段気にしない環境が変わるので面倒臭い

あと開発に関わる人全員が、環境構築ができる訳ではないし

いい加減、環境構築のハードルを下げないとな~と思い、前々から使ってみようと思っていたVagrantを使ってみた


目次


構築する環境

  • DBサーバ(PostgreSQL 9.6)
    • CPU:2コア
    • MEM:1GB
  • nginx(ロードバランサ目的)
    • CPU:1コア
    • MEM:512MB
  • Webサーバ(Tomcat 8.5)×2台
    • CPU:2コア
    • MEM:1GB
  • 全部CentOS7

Vagrantプラグインの追加

設定ファイルの転送等で共有フォルダを使用するが、共有フォルダをマウントする際にエラーが発生する。

エラーを防ぐため、以下のコマンドでプラグインを最初にインストールする。

参考サイト

qiita.com

vagrant plugin install vagrant-vbguest

ベースのbox作成

DBサーバ、WEBサーバを構築する前にCentOS7に共通な設定・更新を実施し、元になるboxを作成する。

※元になるboxを作成する前は、何回も仮想環境を作り直して、新しい仮想PCができるたびに「yum update」やら「Guest Additions」の更新が走ったりとかなり時間がかかった・・・

まず、作業ディレクトリにて「vagrant init」を実行

作成された「Vagrantfile」を以下のように編集した

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "centos/7"

  config.vm.provider "virtualbox" do |vb|
    vb.memory = "512"
  end

  config.vm.provision "shell", inline: <<-SHELL
# いつものやーつ
sudo yum update -y
sudo yum install vim wget -y

# 時刻を日本にする
sudo cp -p /usr/share/zoneinfo/Japan /etc/localtime

# 時刻を自動で調整するようにする
cp /etc/chrony.conf /etc/chrony.conf_preprovision
sudo sed -i -e "s/server 0.centos.pool.ntp.org iburst/server ntp.nict.jp/g" /etc/chrony.conf
sudo sed -i -e "s/server 1.centos.pool.ntp.org iburst/server ntp1.jst.mfeed.ad.jp/g" /etc/chrony.conf
sudo sed -i -e "s/server 2.centos.pool.ntp.org iburst/server ntp2.jst.mfeed.ad.jp/g" /etc/chrony.conf
sudo sed -i -e "s/server 3.centos.pool.ntp.org iburst/server ntp3.jst.mfeed.ad.jp/g" /etc/chrony.conf
sudo chronyc -a makestep

# 空き領域をゼロ埋めするために一時ディレクトリ/tmpに適当なファイルZEROを作成。中身はキッチリゼロ埋めされる。
sudo dd if=/dev/zero of=/tmp/ZERO bs=1M
sudo rm -f /tmp/ZERO
  SHELL

end

空き領域をゼロ埋めしているのは、boxサイズを小さくするため

以下のサイトのコメントを参考にした。

qiita.com

編集が終わったら以下のコマンドを実行し仮想OSを作成する

vagrant up

vagrant up」完了後、パッケージ・boxを作成(名前はmy_base_centos7にした)

vagrant package
vagrant box add my_base_centos7 package.box

もう要らないので後片付け

vagrant destroy

エクスプローラからpackage.boxの削除する

Vagrantfileの作成

Vagrantfileを以下のようにした

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  
  config.vm.box = "my_base_centos7"

  config.vm.synced_folder "./synced", "/vagrant_data", create: true

  config.vm.define :db do |db|
    db.vm.hostname = "mt-db"
    db.vm.provision "db", type: "shell", :path => "create_db_server.sh", :privileged => false
    db.vm.network :forwarded_port, id: "ssh", guest: 22, host: 7022
    db.vm.network "forwarded_port", guest: 5432, host: 5432
    db.vm.network "private_network", ip: "192.168.33.10"
    db.vm.provider "virtualbox" do |db_vb|
      db_vb.name = "mt_db"
      db_vb.cpus = 2
      db_vb.memory = "1024"
    end
  end

  config.vm.define :proxy do |proxy|
    proxy.vm.hostname = "mt-proxy"
    proxy.vm.provision "proxy", type: "shell", :path => "create_proxy_server.sh", :privileged => false
    proxy.vm.network :forwarded_port, id: "ssh", guest: 22, host: 7122
    proxy.vm.network "forwarded_port", guest: 8888, host: 8888
    proxy.vm.network "private_network", ip: "192.168.33.11"
    proxy.vm.provider "virtualbox" do |proxy_vb|
      proxy_vb.name = "mt_proxy"
      proxy_vb.cpus = 1
      proxy_vb.memory = "512"
    end
  end

  config.vm.define :web1 do |web1|
    web1.vm.hostname = "mt-web1"
    web1.vm.provision "web1", type: "shell", :path => "create_web_server.sh", :privileged => false
    web1.vm.network :forwarded_port, id: "ssh", guest: 22, host: 7222
    web1.vm.network "private_network", ip: "192.168.33.12"
    web1.vm.provider "virtualbox" do |web1_vb|
      web1_vb.name = "mt_web1"
      web1_vb.cpus = 2
      web1_vb.memory = "1024"
    end
  end

  config.vm.define :web2 do |web2|
    web2.vm.hostname = "mt-web2"
    web2.vm.provision "web2", type: "shell", :path => "create_web_server.sh", :privileged => false
    web2.vm.network :forwarded_port, id: "ssh", guest: 22, host: 7322
    web2.vm.network "private_network", ip: "192.168.33.13"
    web2.vm.provider "virtualbox" do |web2_vb|
      web2_vb.name = "mt_web2"
      web2_vb.cpus = 2
      web2_vb.memory = "1024"
    end
  end

end

フォルダ構成はこんな感じ

C:.
│  create_db_server.sh---------DB構築スクリプト
│  create_proxy_server.sh------nginx構築スクリプト
│  create_web_server.sh--------Tomcat構築スクリプト
│  Vagrantfile
│
├─.vagrant
│
└─synced--------仮想マシンと同期するフォルダ
    ├─db
    │      sample_db.dump.bz2---------DBダンプ
    │      set_password_to_db.sql-----パスワード設定SQL
    │
    ├─proxy
    │      404.html-------------NotFound画面
    │      maintenance.html-----メンテナンス画面
    │      nginx.conf-----------nginxの設定ファイル
    │      nginx.service--------自動起動の設定ファイル
    │
    └─web
            sample.war----------配置WEBアプリ
            tomcat.service------自動起動の設定ファイル
            tomcat.sh-----------/etc/profile.dに配置

「create_db_server.sh」「create_proxy_server.sh」「create_web_server.sh」には、サーバを構築するためのコマンドが入っている。

あまり参考にならないであろうプロビジョニングスクリプト


create_db_server.sh(PostgreSQL構築)


参考

monakaice88.hatenablog.com


create_proxy_server.sh(ロードバランサnginx構築)


参考

monakaice88.hatenablog.com


create_web_server.sh(Webサーバ構築)


参考

monakaice88.hatenablog.com

終わりに

少々複雑なサーバ構成でも、他の人に渡すときはVagrantfile+αで済むのはありがたいし、変更内容は基本プロビジョニングした時のスクリプトを見れば大丈夫なのは良いですな。

ただプロビジョニングのスクリプトを作成するのには凄い時間が掛かった・・・

最初に共通で使用するboxを作成しておけば、もう少し苦労が少なかったかも