0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS ElastiCache Serverless (Redis OSS) にGoから接続すると「i/o timeout」になる原因と解決方法

0
Posted at

はじめに

こんにちは!AWSを学習中のrikutoです。

先日、ECS FargateにデプロイしたGoアプリがRedis(ElastiCache)に接続できず、起動直後にクラッシュを繰り返す問題に遭遇しました。
ローカルのDockerでは問題なく動いていたのに、AWS上でだけ i/o timeout になるという状況です。

そこで今回は、自分自身の備忘録も兼ねて、

  • なぜこのエラーが起きたのか(原因はElastiCache Serverless(Redis OSS)の仕様でした)
  • どのように原因を切り分けて、解決したのか

ということを整理してまとめてみました。

この記事は、次のような方に向けています。

  • GoアプリケーションをECS Fargateなどにデプロイして、ElastiCacheに接続したい方
  • ローカル(Docker)ではRedisに接続できるのに、AWS上でだけタイムアウトする方

同じようなポイントで詰まっている方の参考になれば幸いです。
もし内容に誤りや、もっと良い方法があれば、ぜひコメントで教えていただけると嬉しいです!

記事の結論

ElastiCache Serverless(Redis OSS)は転送時の暗号化(TLS)が強制的に有効になっています。
そのため、TLSなしで接続しようとすると応答が返らず、i/o timeout になります。

Goの接続コード(infrastructure/redis.go)に、接続先がAWSのときだけTLS設定を渡すよう以下の修正を加えることで解決しました。

infrastructure/redis.go
 package infrastructure

 import (
 	"context"
+	"crypto/tls"
+	"strings"

 	"github.com/iwariku/golang_twitter/utils"

 	"github.com/redis/go-redis/v9"
 )

 func NewRedisClient() *redis.Client {
 	redisAddress := utils.GetEnvOrDefault("REDIS_ADDRESS", "redis_server:6379")

+	// アドレスをベースに、AWS環境(TLS必須)かどうかの判定を行う
+	var tlsConfig *tls.Config
+	if strings.Contains(redisAddress, "amazonaws.com") {
+		tlsConfig = &tls.Config{
+			InsecureSkipVerify: true, // 学習・デバッグ環境のためスキップ
+		}
+	}
+
 	redisClient := redis.NewClient(&redis.Options{
 		Addr:     redisAddress,
 		Password: "",
 		DB:       0,
+		TLSConfig: tlsConfig, // ローカル時はnil(TLS無効)、AWS時は設定済みの値が入る
 	})

 	ctx := context.Background()
 	if err := redisClient.Ping(ctx).Err(); err != nil {
 		panic("Redisへの接続に失敗しました: " + err.Error())
 	}

 	return redisClient
 }

なぜこの修正で直るのか、どうやって原因を絞り込んだのかを、以下で順番に説明していきます。

実行環境

Tool Version
Go 1.25.5
go-redis(github.com/redis/go-redis/v9) v9.17.3
Amazon ElastiCache Serverless(Redis OSS) 7.1
ローカルRedis(Docker) redis:latest
AWS ECS on Fargate -

発生したエラー

ECS FargateにGoアプリケーションをデプロイしたところ、コンテナ起動直後に以下のパニックが発生しました。
タスクは起動とクラッシュ(強制終了)を繰り返す状態でした。

panic: Redisへの接続に失敗しました: dial tcp 10.0.151.216:6379: i/o timeout

なお、ローカルのDocker環境(TLSなしのRedis)では同じコードで問題なく接続できていました。

試したこと:セキュリティグループの切り分け

最初は「セキュリティグループが通信を遮断しているのでは」と疑いました。
そこで原因を切り分けるため、次の2つの仮説を立てて検証しました。

  • A案: セキュリティグループで通信が遮断されている(ネットワークの問題)
  • B案: 通信は届いているが、暗号化などの仕様不一致でRedis側が応答しない(コードの問題)

検証として、Redis側セキュリティグループのインバウンドルールを一時的に変更しました。
ポート 6379 のソースを、Fargate用セキュリティグループからの許可のみ → 0.0.0.0/0(全開放)に変えて再デプロイしました。

結果は、全開放しても全く同じ i/o timeout でした。
これでA案(ネットワーク)が消え、B案(コード側の仕様不一致)に原因を絞り込めました。

なお、検証後はセキュリティグループを必ず元の状態(特定のセキュリティグループからのみ許可)に戻してください。

原因:ElastiCache Serverless(Redis OSS)はTLSが強制有効

AWSの公式ドキュメントを確認したところ、ElastiCache Serverless(Redis OSS)では転送時の暗号化(TLS)が最初から強制的に有効になっていることが分かりました。
これはAWSの仕様であり、ユーザー側で無効にすることはできません。

一方、修正前のGoコードは TLSConfig を設定しておらず、暗号化なしの通常のTCPで接続していました。
TLSを要求するRedis側は暗号化されていない通信に応答を返さないため、アプリ側は接続を待ち続けます。
その結果、エラーは「接続拒否(connection refused)」ではなく i/o timeout として現れます。

解決方法

手順1: 接続先アドレスからTLSの要否を判定する

環境変数 REDIS_ADDRESS の値に amazonaws.com が含まれるかどうかで、AWS環境かを判定します。
AWS環境のときだけ *tls.Config を組み立て、ローカルのときは nil のままにします。

infrastructure/redis.go(抜粋)
var tlsConfig *tls.Config
if strings.Contains(redisAddress, "amazonaws.com") {
	tlsConfig = &tls.Config{
		InsecureSkipVerify: true, // 学習・デバッグ環境のためスキップ
	}
}

手順2: go-redisのOptionsにTLSConfigを渡す

redis.OptionsTLSConfig フィールドに、手順1で組み立てた値を渡します。
nil を渡した場合はTLSなしで接続されるため、ローカルと本番を同じコードで扱えます。

infrastructure/redis.go(抜粋)
redisClient := redis.NewClient(&redis.Options{
	Addr:      redisAddress,
	Password:  "",
	DB:        0,
	TLSConfig: tlsConfig, // ローカル時はnil(TLS無効)、AWS時は設定済みの値が入る
})

補足: InsecureSkipVerify: true について

InsecureSkipVerify: true は、サーバー証明書の検証をスキップする設定です。
そのまま本番運用へコピペしないよう、以下の点に注意してください。

  • なぜ必要か: Goコンテナ内にAWSのルート証明書がないと、証明書検証に失敗して x509 関連のエラーで起動できないため
  • 今回許容した理由: RedisはPrivate Subnet内に配置しており、同じVPC内の経路でしか通信が発生しないため
  • 本番でのベストプラクティス: false にした上で、コンテナ内にCA証明書(ca-certificates パッケージ等)を配置して正しく検証させる

修正後、コードをビルドしてECRにプッシュし、ECSサービスで「新しいデプロイの強制」を実行したところ、タスクのステータスが RUNNING で安定し、起動時のパニックが解消されました。

まとめ

改めて、今回のポイントを振り返ります。

  • ElastiCache Serverless(Redis OSS)はTLSが強制有効のため、TLSなしの接続は i/o timeout になる
  • go-redisでは Options.TLSConfig*tls.Config を渡すだけでTLS接続できる
  • 接続先アドレスで判定すれば、ローカル(TLSなし)とAWS(TLSあり)を同じコードで両立できる
  • i/o timeout はネットワーク起因とは限らない。全開放テストで白黒つけると原因を絞り込める

最後に

最後まで読んでいただきありがとうございました!

「ローカルでは動くのにAWSでは動かない」系のエラーは原因の候補が多く、切り分けの進め方が大事だと実感しました。
この記事が同じエラーで悩んでいる方の助けになれば嬉しいです。

もし内容の誤りや、より良い方法があれば、ぜひコメントで教えてください!

参考リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?