新ポータルサイト運用開始

Djangoで運用してきたmarochanetポータルサイトですが、本日半年かけて独学で構築してきたFastAPI版のポータルサイトとしてリニューアルしました。

左のプレビューリンクですが、さすがオールWEBPACKによるシングルページアプリケーション。文章が拾われない・・・・というか一応トップの画像を拾っていると言うことはJSが動いているところまでプレビュー出来ているのか。

ユーザーログイン機能や各種リンクと、まだまだ機能が少ないなりにもDjango版(こちらも殆ど無機能)と同等以上は役割を果たせるのではということで運用に踏み切りました。

但し、アカウント管理などセキュリティー部分は自作なのでDjango版と比べて信頼性の面ではイマイチということで暫くはログとにらめっこしながらの運用となりそうですw

右がブランドアイコン。所以は非公開ですが、「− Glutton Orca − 暴食のシャチ」という感じで。

前職でファーストペンギンにエライ目に遭ったという苦い経験もあり《ファーストペンギンを屠るものw》という称号も持たせています。

あんまり強そうではないのはご愛敬ということで。

新しいポータルにはポートフォリオということで活動内容を簡単に紹介するような内容を充実させていく予定です。

こちらのブログでチマチマ書いてきたことをまとめる感じですかね。

Glutton Orcaというか、、中二?。

Webpackその他

メモ記事とはいえなかなか書いてる時間がとれず一月以上の間が空いてしまった。FastAPIによるバックエンド実装からTypescriptとSASSによるフロントエンド実装の勉強に入った所、WebAssemblyとその有力言語のRustに興味が写ってしまい、そのあたりを勉強しながらフロントエンドに組み込む技術を調査していたら、Webpackという技術に突き当たった。

WebAssemblyとRustに関しては別の機会に書くとして、今回はWebpack。いままで何度か単語程度に見かけはしたが、なんとなくお腹一杯感でスルーしていたが、必要に応じて調べてみると目からウロコ的な技術だった。

従来の静的コンテンツ(ブラウザ側で処理をする所謂フロントエンド)は、HTMLベースでスタールシート(CSS)やJavascriptを必要に応じて読み込む形式だったが、Webpack化することで、大まかには一つのJavascriptにまとめられる。

例えば下記のようなindex.htmlでは

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <!-- 省略 -->
  <style type="text/css">
    @import "/static/css/bootstrap-custom.css";
  </style>
  <script type="module">
    import { Alert, Button, Carousel, Collapse, Dropdown, Modal, Offcanvas, Popover, ScrollSpy, Tab, Toast, Tooltip } from "/static/js/bootstrap.esm.js"
  </script>
</head>
<body class="base">
  <script type="module">
    import {init} from "/static/js/app.js"
    window.addEventListener("load", init());
  </script>
</body>
</html>

6行目〜でbootstrapのカスタムスタイルシート(これは別途SASSでコンパイルしている)を読み込んで、その下9行目〜でbootstrap用のスクリプトをモジュールとして読み込んでいる。Bodyタグを読み込んだあとで14行目〜でシングルページアプリケーション用スクリプトapp.jsを読み込んでページロード完了後にinitを実行(window.addEventListener)。app.jsもTypescriptのソースから別途コンパイルしておりアプリケーションの規模によっては多くのモジュールに分かれてロードされる。

こういった構成のものが、Webpack化することで下記のように出来る

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <!-- 省略 -->
    <script src="/static/dist/main.js"></script>
</head>
<body class="base">
</body>
</html>

6行目のmain.js を読み込むだけとなる。main.jsはWebpackによってコンパイルされたjavascriptで読み込むべきリソースが全てパック化された生成物となる。ソースファイルは一般にindex.jsとされ、下記のような内容となる

import '../scss/bootstrap-custom.scss';
import 'bootstrap/dist/js/bootstrap.esm.js';

import {init, build} from '../ts/app';

init();

window.addEventListener("load", () => {
  build();
});

一行目でbootstrapのカスタムスタイルシートを読み込むが、ここではscssのソースファイルを直接インポートして、WebpackのプラグインでSassコンパイラを起動する。生成したスタイルシートをインポートすると呼び出し側のHTMLのHEADタグ内にSTYLEタグを生成して直接コーディングするため、CSSファイルは生成されない。

2行目はbootstrap用のスクリプトをインポート。4行目でシングルページアプリケーションスクリプトを読み込むが、これもTypescriptのソースを直接していして内部でコンパイラを起動している。また、従来構成と違って読み込んだ時点ではBODYタグが生成されていないため、DOM関連の初期化があると失敗する。DOM関連の初期化はloadイベントをトリガとして始めるよう工夫が必要。

とりあえず、Webpackを導入するとこうなるというだけのメモで、Webpackの具体的な設定などはまた別の機会に。

Windows11リモート環境構築メモ

プログラミング等の作業用としては現在はM1 MacBookを使用しており、WindowsデスクトップPCは主にゲームなどエンタメ用に使用している。その為置き場所はリビングとなり、作業場と少しばかり離れていたりする。

それに加えて最近WindowsPCの動作が不安定になってきたため、暫くメンテナンスを繰り返すこととなり。最終的にはOS再インストールという結論。そのついでに今まで放置していた問題点など諸々対策することとした。

デスクトップPCは今時少なくなった自作PCで、筐体に至っては15年以上使っている。OSに至ってはVistaから7にアップグレードした際に一度クリーンインストールして以来、全てアップグレードインストールで、M/B&CPU等のハードウェアも2回ほどそのままアップグレード、ハードディスクの交換やSSDへの換装の際もクローンコピーツールを用いて環境を変えずに運用してきた経緯がある。
M/B買ったときにも店員さんに「クリンインストールした方が良い」と散々言われたのに、動くから〜とそのまま結果オーライで運用してきたので、とうとう破綻が来たのかなーくらいの印象。

Windows11をクリーンインストールする際、同じPC(ハードウェア)であればそのまま認証された。またユーザー設定をする際にマイクロソフトアカウントを使用してしまうと、ユーザーフォルダ名が適当な物にされてしまい後で変更するハメとなった。最初はローカルユーザーでセットアップした方が良いかもしれない。

ツールやゲームなどアプリを一通りインストールし直して環境を再現したところで無事終了。

ここからがリモート環境構築

  • Macからのリモートデスクトップ
    • これはMac用のMicrosoft Remote Desktopがあるので使用
  • Macからssh接続
    • Windows側でOpenSSHサーバーをインストール(設定→機能の追加)
    • Windows側でOpenSSHサーバーを起動する(サービスの管理)
    • SSHデフォルトシェルをPowershellにする
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Program Files\PowerShell\7\pwsh.exe" -PropertyType String -Force
  • リモートで電源ON/OFF
    • 電源OFFはリモートデスクトップもしくはSSHからシャットダウンコマンド(PSTOOLS)
    • 電源ONはWindowsPCのBIOS設定でWakeup On LANを有効にする
    • 電源設定で高速起動をOFF
    • デバイスマネージャでネットワークカードの電源管理項目でmagicpacketでの起動を有効にする。また省電力機能をOFFにする
    • magicpacketを発行するスクリプト(Powershell)を作成し、Macから起動することでWindowsPCの電源が入るのを確認(シャットダウン状態、休止状態、スリープ状態それぞれから起動出来る事を確認)
#! /usr/bin/env pwsh
# Wakeup On Lan

# Unicorn
$hostmac="XX-XX-XX-XX-XX-XX"

$mac_addr=@($hostmac)
$header=[byte[]](@(0xff)*6)

foreach ($item in $mac_addr){
    Write-Host "Send magic packet to : " $item
    $addr=[byte[]]($item.split("-") | %{ [Convert]::ToInt32($_,16) });
    $magicpacket = $header + $addr * 16;
    $target = [System.Net.IPAddress]::Broadcast;
    $client = New-Object System.Net.Sockets.UdpClient;
    $client.Connect($target, 2304);
    $client.Send($magicpacket, $magicpacket.Length) | Out-Null
    $client.Close()
    Write-Host "Send magic paket to : " $item -ForegroundColor Green
}

WordPress 5.9

余り使いこなしていないので、イマイチ何が変わったか解らないけど、アナウンスがあったので兎に角アップグレードを行った。サイト改造によりDocker環境での運用のためDockerイメージとしてのwordpressを使用しているが、運用中でもあることなので、普通にダッシュボード画面にてアップグレードで難なく完了。

しかし、ダッシュボードのサイトヘルスステータスを見ると新たなエラーがあった。調べても良く分からないが何やら「intlなるライブラリが足りない」旨のメッセージのようだ。これはwordpress本体の方ではなくサーバー機能の問題ということで、DockerHubへ調査に。wordpress5.9用のfpmイメージがあったのでDockerfileを見てみるとphpのライブラリとしてintlが追加されている。

ということで、Dockerイメージの入れ替え

$ docker-compose down --rmi all

コンテナを停止してイメージ削除、そしてdocker-compose.ymlを編集してをwordpress用phpイメージを5.9に変更

$ docker-compose up -d

にて再起動完了。無事動作することを確認しました。

以前これをやってwordpressのサイトデータがおかしくなった記憶があるので、念のためディレクトリごとバックアップをとっておいたのですが、問題なかったため出番は無し。

mariadbのデータベースも毎日バックアップを取っているので事故があっても面倒なだけで大丈夫な環境にはなっています。

dockerイメージのアップデートは頻繁にあるものもあるので、ときどきはチェックしないといけないと反省。しかもDockerfileやdocker-compose.ymlでのイメージ指定がlatestになっているのが多く、現在のバージョンを調べるのが面倒。しっかりとバージョンを記載したイメージ指定にしないといけないな。。

wordpressのdocker化と既存データの移植

従来環境ではApache2+MySQLでしたが、dockerへの移植時はwebの情報を参考にnginx+php+fpm+mariadbとしました。DockerHubからwordpress+fpmとmariaDBの公式イメージを使用して構築しました。

version: '3.1'
services:
  db:
    image: mariadb:latest
    container_name: mariadb_wp
    volumes:
      - mariaDb:/var/lib/mysql
      - ./db_backup:/backup
    restart: always
    command: --innodb-read-only-compressed=OFF
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: wpdata
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      TZ: Asia/Tokyo
  php-fpm:
    image: wordpress:5.8-fpm
    container_name: php_wp
    volumes:
      - ./blog:/var/www/html
      - ./conf/upload.ini:/usr/local/etc/php/conf.d/upload.ini
    restart: always
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: webdata
      WORDPRESS_DB_PASSWORD: password
      WORDPRESS_DB_NAME: user
      TZ: Asia/Tokyo
  nginx:
    image: nginx
    container_name: nginx_wp
    depends_on:
      - php-fpm
    environment:
      - VIRTUAL_HOST=blog.marochanet.org
      - TZ=Asia/Tokyo
    volumes:
      - ./blog:/var/www/html
      - ./conf/default.conf:/etc/nginx/conf.d/default.conf
      - ./conf/nginx.conf:/etc/nginx/nginx.conf
    expose:
      - '443'
    restart: always

volumes:
  mariaDb:

networks:
  default:
    external:
      name: shared

最後のsharedネットワークへの参加とnginxホストの環境変数VIRTUAL_HOSTでリバースプロキシで構成する仮想ホスト名でアクセス出来るようになっています。この場合ここではport公開せずにexposeを使用します。

mariaDBはwordpressだけではなくその他django、rainloop、nextcloudでも使用するが、とりあえずこのcomposeプロジェクトで立ち上げておく(他のcomposeからもDBホストmariadb_wpでアクセス出来る)。

nginxホストの設定:nginx.conf と default.confはサンプルのまま

$ cat /conf/nginx.conf
user  nginx;
worker_processes  1;
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;
events {
    worker_connections  1024;
}
http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    log_format  main  '$uri - $is_args - args :::'
                      '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    keepalive_timeout  65;
    include /etc/nginx/conf.d/*.conf;
}

$ cat /conf/default.conf
server {
    index index.php;
    charset utf-8;
    server_name localhost;
    root /var/www/html;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    location ~* /wp-config.php {
        deny all;
    }
    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }
    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php-fpm:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
    location ~* .(html|css|js|jpe?g|png|gif|svg|mpg|flv|swf)$ {
        expires max;
        access_log off;
    }
}

このままアクセスすると、言語およびadminのパスワード設定など初期設定が始まる。今回は既存サイトの移植が目的だが、まずはこのまま初期化して素のwordpressがアクセス出来るようにします。そののちwp-contentsを既存サイトのものと置き換えます。この段階では一度ポートを閉じるなどしてアクセス出来ないようにしておく必要があるかも知れません。

$ sudo rm -rf wp-contents
$ sudo cp -rf [old position]/wp-contents .
$ sudo chown -R www-data.www-data wp-contents 

次にデータベースのコピーですが。まずは既存サイトのデータベースをバックアップします。たとえば同じホストのmysqlからバックアップするとして

$ mysql-dump --lock-all-tables -u root -p --databases wpdata > wpdata.sql
Passwrd:

今回はmysqlからmariadbへの移植も兼ねているので、そのままではエラーになるため、キーワードを入替えておく必要がある。

今回の場合ではunicodeの表現方式で

utf8mb4_0900_ai_ci => utf8mb4_unicode_ci

に変換しておく必要があった。

変換の後、コンテナのデータベースにリストアします。メンテナンス用にdbサービスにマウントしているdb_backupにコピーしてから

$ docker-compose exec db /bin/bash
db$ mariadb -u root -p
mariadb> drop database wpdata
mariadb> quit
db$ mariadb -u root -p < /backup/wpdata.sql

すでに作成済のデータベースwpdataを一旦削除してから改めてバックアップデータをリストア

これにて無事移植が完了しました。

ここまで簡単な流れにまとまったけど、これでも結構失敗を繰り返したり、、あーでもないこーでもないと試した結果だったりします。その紆余曲折をまとめられれば良いのだが、やってるときは悩みながら突っ走ってしまうので、過程があまり残らないことが間々あるw

dockerリバースプロキシ環境

前記事で書いたように従来のApache仮想ホストと同じことをdockerブリッジネットワークで構成するためには、リクエストホスト名によって接続コンテナを切り替える機能が必要とのこと。しっかり勉強すれば自力でも出来るかも知れないが、先人の作り上げた構成をお手軽に実現出来るのがdockerの良いところということで、ネット検索でも評判の良いjwilder/nginx-proxyを使用することにしました。

version: '3'
services:
  nginx_root:
    build: ./rootimg
    image: jwilder/nginx-proxy:certbot
    container_name: nginx_root_container
    hostname: nginx_host
    depends_on:
      - http_default
    privileged: true
    environment:
      - DEFAULT_HOST=base.marochanet.org
      - TZ=Asia/Tokyo
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
    networks:
      default:
        ipv4_address: 192.168.29.2
    ports:
      - "80:80"
      - "443:443"
    restart: always
  http_default:
    container_name: http_default_container
    image: nginx
    hostname: http_default_host
    expose:
      - "80"
    environment:
      - VIRTUAL_HOST=base.marochanet.org
      - TZ=Asia/Tokyo
    volumes:
      - ./html:/usr/share/nginx/html
    restart: always

networks:
  default:
    driver: bridge
    name: shared
    ipam:
      config:
        - subnet: 192.168.xx.0/24
          gateway: 192.168.xx.1
          ip_range: 192.168.xx.16/28

nginx_rootというサービスがjwilder/nginx-proxyを使用したリバースプロキシコンテナです。build:でイメージを作り直していますが、Letsencryptの証明書を取得/更新するためにcertbotをインストールしているだけです。

  • rootimg/Dockerfile
FROM jwilder/nginx-proxy

ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update \
    && apt-get install --yes --no-install-recommends \
      certbot \
    && rm -rf /var/lib/apt/lists/*

2個目のサービスhttp_defaultはとりあえずのWEBサーバー。テスト用にHTML返すだけなので特別なことはしていませんが、これだけでOKです。キモはenvironment節の環境変数として記されたVIRTUAL_HOSTで、ここに設定したURL向けのリクエストがこのコンテナに向けられるようにリバースプロキシが上手い具合に設定してくれます。

プロキシサービスのコンテナにある環境変数DEFAULT_HOSTはそのまんまの意味で、行き先の見つからない宛先(IPアドレス直とか)の場合の飛び先で、この場合宛先がhttp_defaultサービスと同じになっているので、迷い込んで来た人?IPアドレスで飛んできたスキャンな人へのメッセージページ/エラーページにすると良いかもしれません。

一番最後のnetworks:ではブリッジネットワークの設定をしています。name:節の shared がネットワーク名になります。サブネット、ゲートウェイが設定出来、さらに ip_rangeは、よく理解出来てませんが、固定IPを割り当てる範囲を決めているようでした。固定IPは 1~15で、DHCP的なアドレス割当ては16以降になっているようです。

基本的にはこの共通ネットワークに入ってさえいれば同じdocker-composeで作成していないサービスでもリバースプロキシの傘下に入ることが出来ます。別プロジェクトのdocker-composeでは、外部参照のネットワークとしてsharedを定義しておきます。

networks:
  default:
    external:
      name: shared

当サイトの他のサービス(djangoサイト、wordpress、rainloop WEBメール、nextcloud)は全てこの方法でリバースプロキシ傘下に入っています。

certbotも入れたので、次はletsencryptでの認証関連の設定となりますが、今回は割愛しています。jwilder/nginx-proxyではお手軽な方法が用意されているので、letsencrypt用の起動スクリプトも含めて次回のメモ記事で紹介します。

docker環境も整って勉強や実験も一段落してきたので、そろそろブログネタの方向性も(趣味的な方向へ)変えていきたいなと思うのですが、コロナとか自粛モードでなかなかネタ集めが出来ないですねー(言い訳
・・・・まぁこのへんのネタもじゅーーーぶんに趣味的ではあるけど。

docker対応ufw(iptabels)設定の件

ここでdockerについての解説をするのはおこがましいので、下記に覚え書きリンクを貼付ける

ローカルLAN環境でWEBサーバーを運用するだけなら良いが、当サイトにdockerを導入するに当たって一番の懸念事項がファイアウォールとの相性問題があった。

当サイトと同じUbuntu20.04LTSで下記のようなファイアウォール設定であったとして

xxxx@user$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
80/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere
80/tcp (v6)                ALLOW       Anywhere (v6)
443/tcp (v6)               ALLOW       Anywhere (v6)

dockerコンテナで8080ポートでwebサイトを立ち上げると、ファイアウォールをするりと抜けて外部に公開できてしまう(下記テキトウdocker-compose.yml)

web:
  image: nginx
  volumes:
   - ./templates:/etc/nginx/templates
  ports:
   - "8080:80"
  environment:
   - NGINX_HOST=foobar.com
   - NGINX_PORT=80

当サイトではufwファイアウォールの運用で特定IPアドレスからのアクセスを遮断している(メールサーバへの不正アクセスやwordpressへの不正ログイン試行を手動・自動で監視して遮断対象アドレスをリストアップ)。

ufwコマンドで設定するポートアクセス許可よりも前で設定するために、/etc/ufw/before.rules に記載のあるチェインufw-before-inputにログ出力と遮断コマンドを追加している。

# blacklist-ipset-S ここにブロック対象のアドレスを追加
-A ufw-before-input -s xxx.xxx.xxx.xxx -j auto-blocklist
# blacklist-ipset-S

# タグをつけたログを残してドロップさせるチェイン
# ファイルの先頭の*filter節に追加する必要あり?
-A auto-blocklist -j LOG --log-prefix "[BLOCKLIST]"
-A auto-blocklist -j DROP

こうすることでufw起動時にiptableの遮断リストが設定される。

しかし、上記のdockerコンテナはこの設定も効かず、遮断対象アドレスも通過してしまう。

そこで、まずdockerコンテナが何故ufwの設定を無視できるのかを調べてみる。(iptableコマンド)

user@server:~$ sudo iptables -nvL
Chain INPUT (policy DROP 2979 packets, 152K bytes)
 pkts bytes target     prot opt in     out     source               destination
3687K 3750M ufw-before-logging-input  all  --  *      *       0.0.0.0/0            0.0.0.0/0
3687K 3750M ufw-before-input  all  --  *      *       0.0.0.0/0            0.0.0.0/0
83453 4149K ufw-after-input  all  --  *      *       0.0.0.0/0            0.0.0.0/0
77337 3836K ufw-after-logging-input  all  --  *      *       0.0.0.0/0            0.0.0.0/0
77337 3836K ufw-reject-input  all  --  *      *       0.0.0.0/0            0.0.0.0/0
77337 3836K ufw-track-input  all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
4406K 5775M DOCKER-USER  all  --  *      *       0.0.0.0/0            0.0.0.0/0
4406K 5775M DOCKER-ISOLATION-STAGE-1  all  --  *      *       0.0.0.0/0            0.0.0.0/0
3634K 4639M ACCEPT     all  --  *      br-a52db8fb2804  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
80854 4814K DOCKER     all  --  *      br-a52db8fb2804  0.0.0.0/0            0.0.0.0/0
 675K 1102M ACCEPT     all  --  br-a52db8fb2804 !br-a52db8fb2804  0.0.0.0/0            0.0.0.0/0
70296 4218K ACCEPT     all  --  br-a52db8fb2804 br-a52db8fb2804  0.0.0.0/0            0.0.0.0/0
71973  318M ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
54110 3031K ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0
    0     0 ufw-before-logging-forward  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 ufw-before-forward  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 ufw-after-forward  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 ufw-after-logging-forward  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 ufw-reject-forward  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 ufw-track-forward  all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain OUTPUT (policy ACCEPT 4 packets, 196 bytes)
 pkts bytes target     prot opt in     out     source               destination
3574K 2258M ufw-before-logging-output  all  --  *      *       0.0.0.0/0            0.0.0.0/0
3574K 2258M ufw-before-output  all  --  *      *       0.0.0.0/0            0.0.0.0/0
27893 2070K ufw-after-output  all  --  *      *       0.0.0.0/0            0.0.0.0/0
27893 2070K ufw-after-logging-output  all  --  *      *       0.0.0.0/0            0.0.0.0/0
27893 2070K ufw-reject-output  all  --  *      *       0.0.0.0/0            0.0.0.0/0
27893 2070K ufw-track-output  all  --  *      *       0.0.0.0/0            0.0.0.0/0

この辺りもこのために改めて勉強したため、理解不足かも知れないが、、、

普通にufwで設定するファイアウォールはINPUTチェインが主で、5行目のufw-before-inputのチェインから繋がっている(当サイトのアクセス遮断リストもここに繋げている)。それに対してdockerのファイアウォールはFORWARDチェインであり、16,20行目のDOCKERチェインでコンテナで設定したポートの待ち受けを実現している。それはufwのFORWARD設定よりも優先しているので、アクセス制限はそれよりも前で行う必要がある。ここでアレ?と思うのがDOCKERチェインよりも前にあるDOCKER-USERチェイン。このチェインの中身は空白で何もせずに帰ってくる仕組み。

ならば、ここに遮断リストを追加してやれば良いんじゃね?という感じで、

$ sudo iptables -A DOCKER-USER -s xxx.xxx.xxx.xxx -j DROP

としてみたら、無事遮断していることを確認(とりあえずスマホのIPアドレスを遮断してみて確認)。

実は英文の方のdocker docsにはそれっぽいことが書いてあった。。

次に問題はこのリストを起動時に読み込ませるにはだけど、これは何のことはなくufwの助けが借りられることが分かった。先の遮断リストをログ出力しつつDROPするチェインを追加した要領でDOCKER-USERチェインも/etc/ufw/before.rulesに記載してしまおうという思いつきで実験、、上手く動作したので下記に示します。

$ sudo cat /etc/ufw/before.rules
# Don't delete these required lines, otherwise there will be errors
# added for docker access limits. 21/08/17 by mamiyan
*filter
:ufw-before-input - [0:0]
:ufw-before-output - [0:0]
:ufw-before-forward - [0:0]
:ufw-not-local - [0:0]
:DOCKER-USER - [0:0]           # <---- これ追加
:auto-blocklist - [0:0]        # <---- ログ出力&DROP処理用チェイン
:docker-blocklist - [0:0]      # <---- 同上
# End required lines

・・・・・
# for blocklist custon chain 追加チェイン
-A auto-blocklist -j LOG --log-prefix "[DROPLIST]"
-A auto-blocklist -j DROP

# for docker custom chain 追加チェイン
-A docker-blocklist -j LOG --log-prefix "[DROPDOCKER]"
-A docker-blocklist -j DROP

# blacklist-ipset-S ここから
-A ufw-before-input -s xxx.xxx.xxx.xxx -j auto-blocklist
-A DOCKER-USER -s xxx.xxx.xxx.xxx -j docker-blocklist
# blacklist-ipset-E ここまでにリスト追記

24行25行のように二つのチェイン向けに遮断アドレスリストを追記して、$ sudo ufw reload でdocker向けのファイアウォールが設定されます。その他のアクセス制限もこの/etc/ufw/before.rules の DOCKER-USERチェインに記載しておけば良いと思います。

この辺りは、ufwを少し触れる程度の知識からiptableの仕組みとチェインを触れる程度までじっくり勉強が出来て良かったと思います。またここでCentOSとかのRHL系でfirewalldとかになると変ってくるのだろうか。そもそもあちら側は相性問題は無いのかな。

docker環境への移行

約一ヶ月ぶりの記事になりますが、夏休みということで。

この夏休みは、雨続きやら新型コロナの蔓延防止やら緊急事態やらの巣ごもりで、夏バテと闘いながら、従来から引続きpython(django)、javascript(Three.js)勉強行いながら、前回の記事でチラッと書いたdockerの勉強・実装実験を行っておりました。もともろvmwareやら仮想環境に興味があったので、比較的敷居が低かった感じで、いろいろ試しているウチに勢い余って、当サイトのWEB環境もApache2の仮想ホスト環境から、dockerコンテナのブリッジネットワーク構成に移行させてしまったというお話。さらにはメールサーバーもdockerコンテナ化・・・8月中に一通り移行を終えたところで一息ついている感じです。

とっかかりは楽しくて次々とdocker化が進んでいったが、やっぱり全く同じ環境を再現しようとすると色々と問題が出てきたり、ネット調査だけでは答えの出ない問題に突き当たったりとネタだけは貯えたので、覚え書きレベルですが追々と記事にして置こうかと思います。

つい今の今も、この記事を書こうとwordpress(これもdockerコンテナ化済み)の記事編集をしていたところ、メディアファイルがアップロード出来ない問題が発生(移行時に試していないのがいけない・・・)。半日がかりの原因究明でしたが、結局はアップロードパスの修正忘れという単純なポカミス。。まぁdockerという新しい環境への移行なので、まずそちら方面を疑ってしまい、思わぬ深みにはまってしまうというよくある罠でした。

今回の移行ですが、従来構成を下記に示す。普通のubuntuのままなのでapache単独サーバーでvhost.confの設定で仮想ホストを構成していた。

従来構成

同様の環境をdockerリバースプロキシ環境に移行した構成を下記に示します。

移行後のdocker構成

dockerブリッジネットワークでほぼ全てのサービスをコンテナ化してリバースプロキシサーバにて80番ポートと443番ポートを受けて文字通りの仮想ホスト構成としている。メールサーバーは受信のために25番ポートを空けているが、こちらは図のように直接外に向けてポートを空けてしまうと、ホスト内のサービスメール(cronやlogwatchなど)が動作しなくなってしまうので、実際はホスト側でもpostfixを動作させホスト内メールの面倒を見ながら外部から来たメールはコンテナのメールサーバーに転送する仕組みとしている。

一ヶ月の試行錯誤の結果ほぼ従来通りの動作を確認できたが、なんか妙に重いかも知れない。。。

久々のブログ記事でいきなりファイルアップロード不可とか、メッチャ焦りました。新環境では十分なテストが必要ですね。メールサーバーのdocker化もテキトウに確認して運用始めては→問題発覚→環境戻し→デバッグやり直し-の繰返しでした。たいていはショウモナイポカミスなんですが、そういうのに限って見つけるのに半日から1日作業となるのが定期。

Windows Terminal

しばらく最新の動向とか触れる機会が少なかったので、最近のツール事情にはいまだに驚かされたりする。当方では趣味でさくらVPSに契約して個人用のサイトを運営をしており、当たり前だが自宅のローカル環境から操作している。今までは通信ソフトのPuttyでサーバーにSSH接続して、サーバー上でemacsなどのエディタで構築作業をおこなっていたが、前回の投稿で書いたようにマイクロソフトのVScodeエディタでSSH接続することでローカル同じ環境で編集作業を行なうことが出来る。

それでも実際ターミナル操作をするときはPuttyを使用していたのだが、今度はWindows Terminalというものを知ることとなる。こちらはWindowsのストアアプリで、コマンドプロンプトからPowershell、WSLも対応しており、プロファイルを追加することでssh接続も可能となる優れもの。

しかもこちらはWindows版のOpenSSHのサービスで動いているので、SSH-Agentサービス(Windowsサービスで自動実行)ログイン時にパスフレーズを入れる必要もないので便利。

ローカル作業時も任意のフォルダで右クリックすると「Windowsターミナルで開く」のメニューが追加されており、Powershellが起動するようになる。(これは、以前よりシフトを押しながら右クリックでPowershellで開くことが可能だったが)

ということで、これも何かあったときのための覚え書き。

VScodeの件もそうだけど、今までは何だったのか?!ってくらい超便利な感じで、きっとまだ触っていないDockerやなんかも同じように感動するんだろうな、、、、と思いつつも、なんだかね。こんなすごい複雑なシステムの開発環境を使って、作っているものと言えば、趣味のオモチャ程度・・・。超豪華な財布の中に2000円くらいしかい入ってない感じw
精進せねばと思うこのごろ。。。

Bootstrap5対応その他

という記事を書こうとWorpressダッシュボードに移動したら、いつのまにかWordpressが5.8にバージョンアップしていた。例によってDBバックアップを取れとの忠告も聞かずに、速攻アップデートボタンポチり。

特に変った様子もないが、変化点は追々チェックするとしよう。

でBootsrtap5対応だが、先月辺りまでのこのサイトの引越作業の時点でベータ版が存在していたが、あまり気にしていなかったところ、ポータルサイト構築を兼ねたWEBアプリ開発の勉強がてら色々調べていたところバージョン5のリリースを知った(今ごろですが)。機能的にはあまり差は無い(というかよく読んでいない)がJQuery依存の削除やIEなど古いブラウザサポート終了など良い感じのアップデートのようなので、まずはdjangoテストサーバー環境で導入実験を行ない、ある程度問題ないことを確認したところで主ブランチに統合して本番環境のポータルサイトでも採用することとした。

見た目はあまり変らないが、テーマのデフォルトの文字サイズなどが異なるため、若干文字が小さかったり、メニューの文字が太字だったりと細かな気にならない違いが見られた。

4.5から5への対応に関する変更点であるが、前回までのSass導入でHTML(テンプレート)にBootstrapのクラスタグを直接書くことは少なくなったので、基本的には階層構造のスタイル定義をしているSassソースファイルを修正することで対応した。

主な違いは、marginやpaddingを表現するクラスで、たとえば右マージンはmr、左マージンはmlというようにrightとleftの頭文字で表現していたが、バージョン5より、左がstart、右がendと表現するようになりそれぞれマージンとしてはms、meと表記するようになった。これはおそらく多言語対応でRTL(右から書く文化)にも対応する事により水平方向の表記は右からと左からというより、始めと終わりという意味で統一したのだろう。

あとハマったのはアコーディオン型のメニューアニメーションの件で、若干名称が異なり、

  • data-toggle → data-bs-toggle
  • data-target → data-bs-target

になっていた。当サイトはこの位しか機能を使っていないので、以上の修正で無事以前と同様に動作するようになった。

というか、せっかくBootstrap5を導入したのに、新機能を調べて(あるのか?)導入実験とかしたいのだが、他にもいろいろやりたいことありで、なかなか優先順位に困っているところ・・・
思いつき順ということもありますが・・・