シリーズ記事一覧
- 第1回:全体像と環境構築
- 第2回:Pod・ReplicaSet・Deployment
- 第3回:Service・Ingress
- 第4回:ConfigMap・Secret・Volume
- 第5回:Namespace・RBAC
- 第6回:トラブルシュートと運用
- 第7回:設計総合演習
📑 目次
- この記事について
- この記事のゴール
- 前提条件
- Docker Composeではこうだった(Before)
- k8sではこうなる(After)
- ハンズオン:Serviceを作って外部からアクセスする
- つまずきポイント(体験談)
- まとめ
- 次回予告
1. この記事について
前回、Deploymentを使ってPodを動かし、Self-healing やスケーリングを体験しました。
チェーンレストランで言えば「店舗にスタッフを配置した」状態です。
でも、お客さんがまだ来れません。
なぜなら、看板も入口もないからです。
今回は「どうやってPodにアクセスするか」= k8sのネットワーキング を調べて試してみました。
Docker Composeでは
ports: "8080:80" の1行で済んでいたアクセス設定が、k8sではなぜ複雑になるのか。
その理由がわかると、k8sの設計思想が一気に見えてきました。
2. この記事のゴール
この記事で書いていること:
- PodのIPに直接アクセスしない理由
- Serviceの4種類の違いの整理
- NodePort経由でnginxにアクセスしてみた話
- Ingressの役割と位置づけ
3. 前提条件
| 項目 | 要件 |
|---|---|
| 前回の完了 | 第2回でDeploymentを理解済み |
| クラスタ状態 | kindクラスタが起動中 |
# WSL2 Ubuntu で実行
# クラスタが動いているか確認
kubectl get nodes
期待される出力:
NAME STATUS ROLES AGE VERSION
my-first-cluster-control-plane Ready control-plane ... v1.xx.x
4. Docker Composeではこうだった(Before)
4-1. Docker Composeのポート設定
Docker Composeでは、ports の1行で外部アクセスが可能になります。
# docker-compose.yml
services:
web:
image: nginx:1.25
ports:
- "8080:80" # ホスト8080 → コンテナ80
http://localhost:8080 で即アクセス。
シンプルで分かりやすい。
4-2. でもこの壁がある
| # | 壁 | Docker Composeの現実 |
|---|---|---|
| 壁1 | コンテナが再起動するとIPが変わる | アクセス先を毎回確認? |
| 壁2 | 複数コンテナへの振り分け | ロードバランサーは自分で用意 |
| 壁3 | 「/api は APIサーバー」「/ はフロント」 | URLベースの振り分けは自前で構築 |
🔰 Memo:
Docker Composeの ports は「1台のコンテナに直結」。
コンテナが複数台あるとき、誰が振り分けるのか?
という問題が残ることに気づきました。
5. k8sではこうなる(After)
5-1. なぜPodに直接アクセスしないのか
🍽️ 比喩:スタッフの個人携帯 vs お店の代表電話
PodにはIPアドレスがあります。
直接アクセスできます。
でも、k8sのPodは使い捨てです。
状況 何が起きるか Self-healing 古いPodが消え、新しいPodが作られる → IPが変わる スケールアウト 新しいPodが追加される → アクセス先が増える ローリングアップデート 全Podが順番に入れ替わる → IPが全部変わる これはスタッフの個人携帯に直接電話するようなもの。
退職(Pod削除)したら番号が変わってしまいます。Service = お店の代表電話番号
スタッフ(Pod)が入れ替わっても、
お客さん(クライアント)は同じ番号に電話すれば繋がる。
誰が応対するかは、裏側で自動的に振り分けてくれる。
5-2. PodのIP問題を図解
PodのIPに直接アクセスするとどうなるか、
Service経由だとどう変わるかを比較してみましょう。
❌PodのIPに直接アクセスする場合:
✅ Service経由でアクセスする場合:
5-3. Serviceの4種類
Serviceには用途に応じた4つの種類があります。
🍽️ 電話システムに例えると、4段階の「公開範囲」があります。
| 種類 | 比喩 | 公開範囲 | 用途 |
|---|---|---|---|
| ClusterIP | 内線電話 | クラスタ内部のみ | Pod間の通信(API → DB など) |
| NodePort | 店の直通電話 | ノードのIP + 指定ポートで外部公開 | 開発・検証環境 |
| LoadBalancer | コールセンター | クラウドLB経由で外部公開 | 本番環境(AWS ALB等) |
| ExternalName | 転送電話 | 外部サービスへのエイリアス | 外部DBや外部APIへの接続 |
🔰 Memo: まず覚えるのは ClusterIP と NodePort の2つ だけで十分だと感じました。
LoadBalancerはクラウド環境(EKS等)で使い、ExternalNameは特殊なケースのようです。
5-4. 4種類の構成図
4種類のServiceがクラスタ内でどう配置されるかを1枚の図で確認します。
🍽️ NodePort は ClusterIP を「内包」しています。
図のように、NodePort の中に ClusterIP が入っている構造です。
NodePort Service を作ると ClusterIP も自動で割り当てられる — 「直通電話を引いたら、内線番号も自動で付いてくる」ということです。
5-5. Ingressとは?
Serviceが「1つのアプリへのアクセス経路」なら、Ingressは「複数のServiceへの振り分け」です。
🍽️ 比喩:商業ビルの総合受付
/apiにアクセス → APIサービスに案内(B1Fの和食店へ)/にアクセス → フロントサービスに案内(2Fのカフェへ)admin.example.com→ 管理画面に案内(3Fの事務所へ)
🔰 Note: Ingressは今回はハンズオンしていません。
「こういう仕組みがある」と押さえておくだけにしました。
実際の構築は、EKS等のクラウド環境で試すのが効率的そうです。
6. ハンズオン:Serviceを作って外部からアクセスする
6-1. 作業ディレクトリ
# WSL2 Ubuntu で実行
mkdir -p ~/k8s-handson/service-ingress
cd ~/k8s-handson/service-ingress
6-2. Step1:Deploymentの作成(前回の復習)
まずアクセス先となるPodを用意します。
以下のファイルを作成:
📂 service-ingress/
└── nginx-deployment.yaml ← Step 1 🆕
# nginx-deployment.yaml
# Deployment = 店長への指示書
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx # ← このラベルが重要(Serviceと紐づく)
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
ポイント:
app: nginxラベルがServiceとの接続に必要
この後のStepで作るServiceはselector: { app: nginx }でPodを探します。
Deployment のtemplate.metadata.labels.app: nginxと一致させることで、
Service → Pod のルーティングが成立します。
# ~/k8s-handson/service-ingress/ で実行
kubectl apply -f nginx-deployment.yaml
kubectl get pods
期待される出力:
NAME READY STATUS RESTARTS AGE
nginx-deployment-xxxxxxxxx-xxxxx 1/1 Running 0 10s
nginx-deployment-xxxxxxxxx-yyyyy 1/1 Running 0 10s
nginx-deployment-xxxxxxxxx-zzzzz 1/1 Running 0 10s
6-3. Step2:ClusterIP Service(内線電話)
クラスタ内部からPodにアクセスするためのServiceを作ります。
以下のファイルを作成:
📂 service-ingress/
├── nginx-deployment.yaml ✅ Step 1
└── nginx-service-clusterip.yaml ← Step 2 🆕
# nginx-service-clusterip.yaml
# ClusterIP Service = お店の内線電話
apiVersion: v1
kind: Service
metadata:
name: nginx-clusterip
spec:
# ClusterIP(デフォルト。省略してもClusterIPになる)
type: ClusterIP
# どのPodに振り分けるか(ラベルで紐づけ)
selector:
app: nginx # ← Deploymentの labels と一致させる
# ポート設定
ports:
- port: 80 # Serviceが受け付けるポート
targetPort: 80 # Pod側のポート
6-3-1. マニフェストの構造解説
| YAMLの項目 | 比喩 | 意味 |
|---|---|---|
kind: Service |
「電話回線の申請書です」 | リソースの種類 |
type: ClusterIP |
「内線電話でお願いします」 | クラスタ内部のみ |
selector: app: nginx |
「app: nginx のタグが付いたスタッフに繋いで」 | 振り分け先の指定 |
port: 80 |
「代表電話の番号は80で」 | Serviceのポート |
targetPort: 80 |
「スタッフの内線番号は80で」 | Podのポート |
# ~/k8s-handson/service-ingress/ で実行
# Serviceを作成
kubectl apply -f nginx-service-clusterip.yaml
# Serviceの確認
kubectl get service
期待される出力:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP ...
nginx-clusterip ClusterIP 10.96.xxx.xxx <none> 80/TCP 5s
6-3-2. 内部からアクセスしてみる
ClusterIPはクラスタ内部からのみアクセスできます。
一時的なPodを作ってcurlで確認します。
# 一時的なPodからService経由でアクセス
kubectl run curl-test \
--image=curlimages/curl \
--rm -it \
--restart=Never \
-- curl nginx-clusterip:80
期待される出力:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
Service経由でnginxにアクセスできました。
💡 タイムアウトした場合 → セクション7-1(Selectorのミスマッチ)を確認
🍽️ 内線電話(ClusterIP)で「代表番号(nginx-clusterip)」にかけたら、スタッフ(Pod)の誰かが応答してくれた、ということです。
🔰 Note: ここで大事だなと思ったのが、nginx-clusterip:80 という名前でアクセスしている点。
PodのIPアドレスは一切使っていません。
Podが再作成されてIPが変わっても、この名前は変わらないわけです。
6-4. Step3:NodePort Service(直通電話)
次に、クラスタの外側からアクセスできるServiceを作ります。
以下のファイルを作成:
📂 service-ingress/
├── nginx-deployment.yaml ✅ Step 1
├── nginx-service-clusterip.yaml ✅ Step 2
└── nginx-service-nodeport.yaml ← Step 3 🆕
# nginx-service-nodeport.yaml
# NodePort Service = お店の直通電話
apiVersion: v1
kind: Service
metadata:
name: nginx-nodeport
spec:
# NodePort(外部からアクセス可能)
type: NodePort
# どのPodに振り分けるか
selector:
app: nginx
# ポート設定
ports:
- port: 80 # Service内部のポート
targetPort: 80 # Pod側のポート
nodePort: 30080 # 外部公開ポート(30000-32767の範囲)
| ClusterIPとの違い | 内容 |
|---|---|
type: NodePort |
外部公開モード |
nodePort: 30080 |
ノードの30080番ポートで外部に公開 |
# ~/k8s-handson/service-ingress/ で実行
# Serviceを作成
kubectl apply -f nginx-service-nodeport.yaml
# Serviceの確認
kubectl get service
期待される出力:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP ...
nginx-clusterip ClusterIP 10.96.xxx.xxx <none> 80/TCP 2m
nginx-nodeport NodePort 10.96.yyy.yyy <none> 80:30080/TCP 5s
80:30080/TCP の表示に注目。
「Serviceの80番ポート」が「ノードの30080番ポート」に紐づいています。
6-4-1. kindでの外部アクセス方法
kindは Docker 内でクラスタを動かしているため、localhost:30080 では直接アクセスできません。
kubectl port-forward を使います。
# port-forward でローカルからアクセス可能にする
kubectl port-forward service/nginx-nodeport 8080:80
別のターミナルを開いて:
# 別ターミナルで実行
curl http://localhost:8080
期待される出力:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
外部からnginxにアクセスできました。
Ctrl + C でport-forwardを終了してください。
🍽️ 直通電話(NodePort)を引いたので、外部のお客さんからも注文が取れるようになりました。
6-5. Step4:Ingressの概念理解
IngressはStep2/3の「Service」のさらに前段に位置する、URLベースの振り分け(ルーティング)機能です。
6-5-1. Service と Ingress の使い分け
| 観点 | Service | Ingress |
|---|---|---|
| 振り分け単位 | ポート番号 | URLパス / ホスト名 |
| 対象 | 1つのアプリ | 複数のService |
| 比喩 | 各店舗の電話番号 | 商業ビルの総合受付 |
| 設定例 | :80 → nginx Pods |
/api → api-svc、/ → frontend-svc
|
| AWS連携 | - | ALB Ingress Controller でAWS ALBと統合 |
🔰 Note: Ingressのハンズオンはここでは省略しました。
kindでIngress Controller を動かすには追加設定が必要で、本質の理解から外れると判断したためです。
実務ではAWS EKS + ALB Ingress Controller の組み合わせがよく使われるようです。
6-6. 後片付け
# 全てのリソースを削除
kubectl delete -f nginx-service-nodeport.yaml
kubectl delete -f nginx-service-clusterip.yaml
kubectl delete -f nginx-deployment.yaml
# 確認
kubectl get all
7. つまずきポイント(体験談)
7-1. ServiceがPodに繋がらない(Selectorのミスマッチ)
症状: curlするとタイムアウトする。
原因: Serviceの selector と Podの labels が一致していない。
# Podのラベルを確認
kubectl get pods --show-labels
# Serviceのselectorを確認
kubectl describe service nginx-clusterip | grep Selector
対策: 両方の app: nginx が一致しているか確認。大文字小文字も区別されるので注意。
7-2. NodePortのポート番号が範囲外
症状:
The Service "nginx-nodeport" is invalid: spec.ports[0].nodePort: Invalid value: 80: provided port is not in the valid range. The range of valid ports is 30000-32767
対策: nodePort は 30000〜32767 の範囲内で指定してください。
7-3. kindでNodePortに直接アクセスできない
症状: curl localhost:30080 が繋がらない。
原因: kindはDockerコンテナ内でクラスタを動かしているため、ホストから直接NodePortにアクセスできません。
対策: kubectl port-forward を使うか、kind作成時に extraPortMappings を設定する方法があります。
# kind-config.yaml(クラスタ作成時に使用)
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30080
hostPort: 30080 # ホストの30080 → kindの30080
# この設定でクラスタを作り直す場合
kind delete cluster --name my-first-cluster
kind create cluster \
--name my-first-cluster \
--config kind-config.yaml
8. まとめ
8-1. この記事でやったこと
| # | 内容 | 状態 |
|---|---|---|
| 1 | PodのIPに直接アクセスしない理由を理解した | ✅ |
| 2 | ClusterIP Serviceで内部アクセスを体験した | ✅ |
| 3 | NodePort Serviceで外部アクセスを体験した | ✅ |
| 4 | Ingressの役割と位置づけを理解した | ✅ |
8-2. ネットワーキング全体像(まとめ図)
8-3. Docker Compose → k8s 対応表
| Docker Compose | Kubernetes | 備考 |
|---|---|---|
ports: "8080:80" |
Service(NodePort) | k8sは「名前」でアクセス |
| コンテナのIPに直アクセス | やらない | PodのIPは使い捨て |
| (該当なし) | ClusterIP | 内部通信の仕組み |
| (該当なし) | Ingress | URLベースのルーティング |
docker-compose.yml 1ファイル |
Deployment + Service + Ingress | 関心事ごとにファイルを分ける |
9. 次回予告
第4回:ConfigMap / Secret / Volume — 設定とデータの分離
アプリが動き、外部からアクセスもできるようになりました。
でもまだ足りないものがあります。
🍽️ レストランは開店したけど、メニュー表(設定ファイル)とレシピ(秘密情報)がスタッフの頭の中にしかない。
スタッフが入れ替わったらどうする?
次回は:
- なぜ設定をコンテナイメージに埋め込まないのか?
- ConfigMap で環境変数や設定ファイルを外部化
- Secret でパスワード・APIキーを安全に管理
- Volume でデータの永続化