AWSのS3にはPresigned URLという署名付きURLを発行し、一時的なアクセス権を付与する仕組みがあります。これを使うと、例えばAPIサーバーが特定の画像へのアクセス権を発行し、クライアントはプロキシを経由せずに直接S3から画像を取得するといったことができます。
しかし、この機能をKubernetesでホストしているMinio(S3互換のオブジェクトストレージ)で使おうとすると問題が起きます。AWS SDKは AWS_ENDPOINTS
で指定されたURLをベースに署名付きURLを発行します。そのため、 AWS_ENDPOINTS=http://minio.foo.svc.cluster.local:9000
と指定されていると署名付きURLはこのURLをベースに発行されるのでクライアントからアクセスできません。署名付きは特定のオブジェクトへのパスだけでなくプロトコルやホスト名も含めて行われるため、プロトコルを https
に書き換えたり、ホスト名を minio.example.com
に書き換えると署名の検証に失敗します。AWS_ENDPOINTS=https://minio.example.com
と指定すると一応動きますが、APIサーバーからminioへのアクセスもインターネット経由になってしまうためあまり効率的ではありません。
また、アプリケーションコードを変更して AWS_ENDPOINTS_FOR_PRESIGNED
のような環境変数を追加することで対応することもできますが、配布されているDockerイメージを変更するのはメンテナンスがめんどくさいので避けたいです。そこで今回はk8sのCoreDNSにレコードを追加しつつ、minioをhttpsで動かすことで対応します。
まずアプリケーションの AWS_ENDPOINTS=http://minio.foo.svc.cluster.local:9000
を AWS_ENDPOINTS=https://minio.example.com
に修正し、とりあえず動くようにします。
また、minioのsvcのportを443に設定しておきましょう。
apiVersion: v1
kind: Service
metadata:
name: minio
spec:
ports:
- port: 443
name: s3
targetPort: 9000
kube-system
の coredns
cmに以下の設定を追加することでクラスタ内から minio.example.com
にアクセスしたときはクラスタのネットワーク経由で、クラスタ外から minio.example.com
にアクセスしたときはインターネット経由でMinioにアクセスすることができます。
.:53 {
rewrite name minio.example.com minio.foo.svc.cluster.local
}
k3sであれば元々あるcmを修正しなくても以下のリソースを作成すれば対応できます。
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns-custom
namespace: kube-system
data:
minio.override: |
rewrite name minio.example.com minio.foo.svc.cluster.local
minioは秘密鍵を設定することでhttpsで動かすことができます。cert-manager+ingressを使っているなら、既にingressで使っている証明書があるはずなので、それをminioのPodに設定します。今回はcert-managerで発行した証明書の名前を minio-ingress-cert
としたときの設定です。
# ...
volumeMounts:
- name: minio-ingress-cert
mountPath: "/etc/minio/certs"
readOnly: true
- name: ca-dir
mountPath: "/etc/minio/certs/CAs"
readOnly: true
# ...
volumes:
- name: minio-ingress-cert
secret:
secretName: minio-ingress-cert
defaultMode: 420
items:
- key: tls.crt
path: public.crt
- key: tls.key
path: private.key
- name: ca-dir
emptyDir: {}
# ...
minio server \
# ...
--certs-dir=/etc/minio/certs # 追加
/etc/minio/certs/CAs
ディレクトリが存在しないとMinioがディレクトリを作ろうとするがreadonly filesystemなため作れずエラーで起動しません。そのためemptyDirを適当に作ってあげています。
また、ingressのアノテーションに nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
も追加しておきます。
この設定をすることで同じエンドポイントを使いつつクラスタの内外で別のルーティングを行うことで効率的にアクセスすることができるようになりました。Minioに限らず使える方法だと思います。
実はこの記事を書き始めた時点ではcert-managerで自己証明書を発行してアプリケーションPodの initContainers
で SSL_CERT_FILE
に設定してあるファイルと自己証明書の ca.crt
をマージして…という方法を取っておりもう少し長い記事になる予定でした。しかし記事を書いている途中で「これ自己証明書を使わなくても正規の証明書既にあるじゃん」と気づいてしまい、修正したら頑張って書いたマニフェストの大半が消え、なんか記事も短くなってしまいました。まあ自己証明書を使う方法だとライブラリによってはSSL_CERT_FILE
環境変数に対応していなかったりとコンテナイメージ依存度が上がるので、こちらのほうが綺麗かつ確実な方法だと思います。