CLOVER🍀

That was when it all began.

Sentryでアラート検知に合わせてメールを送信してみる

これは、なにをしたくて書いたもの?

Sentryへのログ送信に合わせて、アラートメールを送ってみようかなと。

以前にLogbackを使って、Sentryにログイベントを送信するところはやりました。

Sentry JavaのLogbackインテグレーションを試す - CLOVER🍀

こちらをもとにして、アラートメール送信をやってみます。

Sentryのインストール

こちらに沿って、Sentryをローカルインストールします。

GitHub - getsentry/onpremise: Sentry On-Premise setup

$ git clone https://github.com/getsentry/onpremise.git
$ cd onpremise
$ ./install.sh

今回インストールしたSentryのバージョンは、9.1.2です。

この時、ユーザーを作成することができるので、以下の情報で作成。

「user001@example.com / password」

また、メールサーバーにはMailCatcher 0.7.0を使用し、192.168.0.2で動作しているものとします。

これに合わせて、docker-compose.ymlを以下のように修正します。

x-defaults: &defaults
  restart: unless-stopped
  build:
    context: .
    args:
      SENTRY_IMAGE: ${SENTRY_IMAGE}
  depends_on:
    - redis
    - postgres
    - memcached
    - smtp
  env_file: .env
  environment:
    SENTRY_MEMCACHED_HOST: memcached
    SENTRY_REDIS_HOST: redis
    SENTRY_POSTGRES_HOST: postgres
    SENTRY_EMAIL_HOST: 192.168.0.2
  volumes:
    - sentry-data:/var/lib/sentry/files

メールサーバーの接続先だけ変更すればOKです。

起動。

$ docker-compose up

ところで、ドキュメントを読んでいるとconfig.ymlで設定しそうな雰囲気もあったのですが、今回こちらは設定がうまく反映されません
でした…。

Configuring Sentry / Mail

###############
# Mail Server #
###############

mail.backend: 'smtp'  # Use dummy if you want to disable email entirely
mail.host: '192.168.0.2'
mail.port: 1025
# mail.username: ''
# mail.password: ''
# mail.use-tls: false
# The email address to send on behalf of
mail.from: 'sentry-admin@localhost.localdomain'

とりあえず、Sentryにアクセスしてみます。「http://localhost:9000」にすると、こんな感じの画面が表示されるのでログイン。

f:id:Kazuhira:20191012155246p:plain

メール送信に関する続きの設定を求められるので、ここで設定します。メールサーバーもここで設定できていいと
思うんですけどね?

f:id:Kazuhira:20191012155542p:plain

なお、MailCatcherのSMTPポートは、1025です。

Sentryのプロジェクトを作成します。

f:id:Kazuhira:20191012155638p:plain

f:id:Kazuhira:20191012155726p:plain

こちらを利用する、サンプルコードを書いていきましょう。

サンプルプロジェクト

サンプルプロジェクトは、前回のエントリを少し変えた形で。

Maven依存関係。

        <dependency>
            <groupId>io.sentry</groupId>
            <artifactId>sentry-logback</artifactId>
            <version>1.7.27</version>
        </dependency>

リクエストパスに応じて、各ログレベルのイベントを発生させるようなサンプルアプリケーション。ログメッセージは、QueryStringで
指定できるようにしました。
src/main/java/org/littlewings/sentry/logback/Server.java

package org.littlewings.sentry.logback;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import com.sun.net.httpserver.HttpServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

public class Server {
    static Logger logger = LoggerFactory.getLogger(Server.class);

    public static void main(String... args) throws IOException {
        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 10);
        server.createContext("/", exchange -> {
            MDC.put("app", "java-app");

            String path = exchange.getRequestURI().getPath();
            String query = exchange.getRequestURI().getQuery();

            Map<String, String> queryValues = new HashMap<>();
            if (query != null) {
                for (String kvPair : query.split("&")) {
                    String[] kv = kvPair.split("=");
                    queryValues.put(kv[0], kv[1]);
                }
            }

            Optional<String> logMessage = Optional.ofNullable(queryValues.get("m"));
            

            logger.info("{} access", path);

            byte[] body;
            switch (path) {
                case "/info":
                    infoLogging(logMessage);

                    body = "OK!!".getBytes(StandardCharsets.UTF_8);
                    exchange.sendResponseHeaders(200, body.length);
                    exchange.getResponseBody().write(body);
                    break;
                case "/warn":
                    warnLogging(logMessage);

                    body = "WARN!!".getBytes(StandardCharsets.UTF_8);
                    exchange.sendResponseHeaders(500, body.length);
                    exchange.getResponseBody().write(body);
                    break;
                case "/error":
                    errorLogging(logMessage);

                    body = "ERROR!!".getBytes(StandardCharsets.UTF_8);
                    exchange.sendResponseHeaders(500, body.length);
                    exchange.getResponseBody().write(body);
                    break;
                case "/exception":
                    String em = Optional.ofNullable(queryValues.get("em")).orElse("Oops!!");
                    exceptionLogging(em, logMessage);

                    body = em.getBytes(StandardCharsets.UTF_8);
                    exchange.sendResponseHeaders(500, body.length);
                    exchange.getResponseBody().write(body);
                    break;
                default:
                    exchange.sendResponseHeaders(404, 0);
                    break;
            }

            MDC.clear();
        });

        server.start();
    }

    private static void infoLogging(Optional<String> logMessage) {
        logger.info(logMessage.orElse("INFOログだよ"));
    }

    private static void warnLogging(Optional<String> logMessage) {
        logger.warn(logMessage.orElse("WARNログです"));
    }

    private static void errorLogging(Optional<String> logMessage) {
        logger.error(logMessage.orElse("エラーですよ"));
    }

    private static void exceptionLogging(String exceptionMessage, Optional<String> logMessage) {
        logger.error(logMessage.orElse("例外だよ"), new RuntimeException(exceptionMessage));
    }
}

Logbackの設定。ここに、Sentryの設定も入ります。 src/main/resources/logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5level] [%X{app}] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="Sentry" class="io.sentry.logback.SentryAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>WARN</level>
        </filter>
    </appender>

    <root level="info">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="Sentry"/>
    </root>
</configuration>

これで、起動。作成したプロジェクトのDSNを指定してください。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.sentry.logback.Server -Dsentry.dsn=http://c2cdf4bc618c4b8181d9c0e1e15be772@localhost:9000/2

とりあえず、WARNレベルのログイベントを発生させてみます。

$ curl localhost:8080/warn?m=warn0001
WARN!!

これで、Sentryにログイベントが送信され、

f:id:Kazuhira:20191012160108p:plain

メールも通知されます。

f:id:Kazuhira:20191012160210p:plain

これは、Sentryでデフォルトでプロジェクト内の全メンバーに通知が行われるようになっているからです。

Alert Notifications - Docs

ちなみに、この状態で再度イベントを発生させても、通知が来なかったりします。

$ curl localhost:8080/warn?m=warn0002
WARN!!
$ curl localhost:8080/warn?m=warn0003
WARN!!
$ curl localhost:8080/error?m=error0001
ERROR!!

f:id:Kazuhira:20191012160445p:plain

f:id:Kazuhira:20191012160523p:plain

ちなみに、イベントをクローズ後に再度発生させたりすると、メールが来ますよ。

f:id:Kazuhira:20191012160604p:plain

f:id:Kazuhira:20191012160707p:plain

これは、デフォルトのアラートルールがプロジェクトで最初のイベントを通知するようになっているからですね。

Alert Notifications - Docs

f:id:Kazuhira:20191012161034p:plain

これを変更するには、カスタムアラートルールを作ることになるみたいです。

Creating Custom Alert Notifications - Docs

Routing Alert Notifications - Docs

f:id:Kazuhira:20191012161751p:plain

あとは、メール配信のタイミングを調整したりすると良いかもしれません。

f:id:Kazuhira:20191012161111p:plain

ややハンパですが、今回はここまでで。

Squidをリバースプロキシサーバーとして使う

これは、なにをしたくて書いたもの?

この前、Squidをインストールしてフォワードプロキシを構成してみました。

Ubuntu Linux 18.04 LTSにSquidをインストールして、フォワードプロキシサーバーとして使う - CLOVER🍀

今度は、リバースプロキシを構成してみようと思います。最低限の設定でいってみましょう。

お題

Squidの裏にnginxを置いて、こちらにクライアントからアクセスしてみます。

環境

今回の環境は、こちら。Ubuntu Linux 18.04 LTSです。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.3 LTS
Release:    18.04
Codename:   bionic

Squidのバージョンは、3.5.27。

$ squid -v 
Squid Cache: Version 3.5.27
Service Name: squid
Ubuntu linux
configure options:  '--build=x86_64-linux-gnu' '--prefix=/usr' '--includedir=${prefix}/include' '--mandir=${prefix}/share/man' '--infodir=${prefix}/share/info' '--sysconfdir=/etc' '--localstatedir=/var' '--libexecdir=${prefix}/lib/squid3' '--srcdir=.' '--disable-maintainer-mode' '--disable-dependency-tracking' '--disable-silent-rules' 'BUILDCXXFLAGS=-g -O2 -fdebug-prefix-map=/build/squid3-VfiiQb/squid3-3.5.27=. -fstack-protector-strong -Wformat -Werror=format-security -Wno-error=deprecated -Wno-error=format-truncation -Wdate-time -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,--as-needed' '--datadir=/usr/share/squid' '--sysconfdir=/etc/squid' '--libexecdir=/usr/lib/squid' '--mandir=/usr/share/man' '--enable-inline' '--disable-arch-native' '--enable-async-io=8' '--enable-storeio=ufs,aufs,diskd,rock' '--enable-removal-policies=lru,heap' '--enable-delay-pools' '--enable-cache-digests' '--enable-icap-client' '--enable-follow-x-forwarded-for' '--enable-auth-basic=DB,fake,getpwnam,LDAP,NCSA,NIS,PAM,POP3,RADIUS,SASL,SMB' '--enable-auth-digest=file,LDAP' '--enable-auth-negotiate=kerberos,wrapper' '--enable-auth-ntlm=fake,smb_lm' '--enable-external-acl-helpers=file_userip,kerberos_ldap_group,LDAP_group,session,SQL_session,time_quota,unix_group,wbinfo_group' '--enable-url-rewrite-helpers=fake' '--enable-eui' '--enable-esi' '--enable-icmp' '--enable-zph-qos' '--enable-ecap' '--disable-translation' '--with-swapdir=/var/spool/squid' '--with-logdir=/var/log/squid' '--with-pidfile=/var/run/squid.pid' '--with-filedescriptors=65536' '--with-large-files' '--with-default-user=proxy' '--enable-build-info=Ubuntu linux' '--enable-linux-netfilter' 'build_alias=x86_64-linux-gnu' 'CFLAGS=-g -O2 -fdebug-prefix-map=/build/squid3-VfiiQb/squid3-3.5.27=. -fstack-protector-strong -Wformat -Werror=format-security -Wall' 'LDFLAGS=-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,--as-needed' 'CPPFLAGS=-Wdate-time -D_FORTIFY_SOURCE=2' 'CXXFLAGS=-g -O2 -fdebug-prefix-map=/build/squid3-VfiiQb/squid3-3.5.27=. -fstack-protector-strong -Wformat -Werror=format-security -Wno-error=deprecated -Wno-error=format-truncation'

nginxのバージョンは、こちら。

$ nginx -v
nginx version: nginx/1.17.4

各サーバーのIPアドレスは、以下とします。

  • Squid … 172.17.0.2
  • nginx … 172.17.0.3
  • クライアント … 172.17.0.4

Squidをリバースプロキシとして設定する

では、Squidをリバースプロキシとして設定していきましょう。

まず、デフォルトの設定はこちら。

$ grep -v '^#' /etc/squid/squid.conf | grep -v '^$'
acl SSL_ports port 443
acl Safe_ports port 80      # http
acl Safe_ports port 21      # ftp
acl Safe_ports port 443     # https
acl Safe_ports port 70      # gopher
acl Safe_ports port 210     # wais
acl Safe_ports port 1025-65535  # unregistered ports
acl Safe_ports port 280     # http-mgmt
acl Safe_ports port 488     # gss-http
acl Safe_ports port 591     # filemaker
acl Safe_ports port 777     # multiling http
acl CONNECT method CONNECT
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow localhost manager
http_access deny manager
http_access allow localhost
http_access deny all
http_port 3128
coredump_dir /var/spool/squid
refresh_pattern ^ftp:       1440    20% 10080
refresh_pattern ^gopher:    1440    0%  1440
refresh_pattern -i (/cgi-bin/|\?) 0 0%  0
refresh_pattern (Release|Packages(.gz)*)$      0       20%     2880
refresh_pattern .       0   20% 4320

設定の最初の方で、アクセス許可のためにACLの定義を行います。

acl localnet src 172.17.0.0/16

続いて、「http_access deny all」の前に定義したACLをallowで設定します。

http_access allow localnet
http_access allow localhost
http_access deny all

次に、Squidを「Accelerator / reverse proxy mode」に設定します。「http_port」で、「accel」を加えればOKです。

http_port 3128 accel

squid : http_port configuration directive

プロキシ先のサーバーの定義は、「cache_peer」で行います。

cache_peer 172.17.0.3 parent 80 0 no-query originserver

squid : cache_peer configuration directive

オリジンサーバーに単純に向けるので、typeを「parent」として転送先ポートを80、ICPはポートを0、オプションをno-queryにします。
originserverは、Acceleratorでのオリジンサーバーを指します。

ICPというのは、Internet Cache Protocolのことでキャッシュサーバー間で使用されます。RFCもあります。

http://www5d.biglobe.ne.jp/stssk/nro/rfc2186-j.txt

http://www5d.biglobe.ne.jp/stssk/nro/rfc2187-j.txt

今回は、ICPについてはパス。

これでSquidに設定を反映すれば、リバースプロキシのできあがりです。

$ curl 172.17.0.2:3128
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

この状態でもキャッシュは効くので、これを無効(オリジンサーバーへ常にアクセスさせる)にしたければ、cache denyを設定します。

acl ignore_cache urlpath_regex .+
cache deny ignore_cache

今回は、どのリクエストパスであってもキャッシュを使わないようにしました。

squid : cache configuration directive

これで、どのリクエストもオリジンサーバーまでアクセスが行われるようになります。

「/squid-internal-dynamic/netdb」?「/squid-internal-periodic/store_digest」?

ところで、この状態でSquid経由でnginxにアクセスすると、nginx側のアクセスログに、こんな感じの内容が出力されます。

172.17.0.2 - - [07/Oct/2019:14:20:24 +0000] "GET /squid-internal-dynamic/netdb HTTP/1.1" 404 153 "-" "-" "::"

「/squid-internal-dynamic/netdb」というファイルにアクセスしようとしているようです。

また、少し設定を変えると、以下のようなログが出たり。

172.17.0.2 - - [07/Oct/2019:14:31:26 +0000] "GET /squid-internal-periodic/store_digest HTTP/1.1" 404 153 "-" "-" "::"

これは、キャッシュダイジェストのリクエスト(store_digest)と、ICMP RTTデータベースへのリクエスト(netdb)を無効にすると
(nginx側に)出力されなくなくなります。

cache_peer 172.17.0.3 parent 80 0 no-query originserver no-digest no-netdb-exchange

「no-digest」でstore_digest、「no-netdb-exchange」でnetdbへのアクセスが抑止されます。

今回は複数のSquidを協調させるわけでもないので、オフにしてもよいでしょう。