水曜日, 8月 13, 2025
水曜日, 8月 13, 2025
- Advertisment -
ホームニューステックニュース新卒エンジニアが Claude Code にコントリビュートした話 - Docker DNS問題を解決できた🎊

新卒エンジニアが Claude Code にコントリビュートした話 – Docker DNS問題を解決できた🎊


はじめに

今年、GMOメディア株式会社に入社した新卒1年目のエンジニアの水崎です。
入社してまだ数ヶ月ですが、Anthropic社のClaude Codeというツールにコントリビュートしました。
会社の新卒研修で学んだDockerを含めたコンテナとネットワークの知識が直接活きた瞬間でした。
今回は、新卒でもOSSに貢献できるという実体験を共有します。

Claude Codeとは?

まず、Claude Codeについて簡単に説明します。
Claude Code / Github

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows — all through natural language commands. Use it in your terminal, IDE, or tag @claude on Github.

とあるようにターミナルから操作できるAIエージェントコーディングツールです。

ClaudeCode公式のgif(サイズ圧縮しています)
Claudeがコードを生成し、ファイルの作成・編集を自動で行ってくれます。プロジェクト全体のコンテキストを理解して作業してくれるのが特徴です。

問題の発見 – Dockerコンテナ間で通信ができない

起こったこと

全社でClaude Codeの導入が始まったが、Claude Codeに実際のコードやファイルを触らせるため、なるべくセキュアな環境を用意したいとなりました。
そこで、公式ドキュメントにあるように、コンテナの例が示されていたので参考にし開発環境を作ることにしました。
init-firewall.shをコンテナに対して実行して、ネットワークのセキュリティもしっかりさせました。
しかし、バックエンドを立ち上げるとさっきまで出来ていたDBに接続できなくなりました。

原因調査

新卒研修で学んだネットワーク知識を思い出しながら調査しました。
init-firewall.shでネットワークに対するセキュリティを強化した後だったので、問題はネットワークっぽいと判断しました。


$ nslookup google.com
;; connection timed out; no servers could be reached


$ nslookup google.com 8.8.8.8
Name:	google.com
Address: 142.251.222.14
-> 繋がる


$ cat /etc/resolv.conf
nameserver 127.0.0.11  
  • DockerはコンテナにデフォルトでDNSサーバー(127.0.0.11)を提供
  • NATルールが壊れると、DNS解決ができなくなる

原因の特定 – firewall.shの落とし穴

なぜこれが問題なのか

Dockerは以下のようなNATルールでDNSを実現しています:



:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:DOCKER_OUTPUT - [0:0]
:DOCKER_POSTROUTING - [0:0]
-A OUTPUT -d 127.0.0.11/32 -j DOCKER_OUTPUT
-A POSTROUTING -d 127.0.0.11/32 -j DOCKER_POSTROUTING


-A DOCKER_OUTPUT -d 127.0.0.11/32 -p tcp -m tcp --dport 53 -j DNAT --to-destination 127.0.0.11:34877
-A DOCKER_OUTPUT -d 127.0.0.11/32 -p udp -m udp --dport 53 -j DNAT --to-destination 127.0.0.11:53633


-A DOCKER_POSTROUTING -s 127.0.0.11/32 -p tcp -m tcp --sport 34877 -j SNAT --to-source :53
-A DOCKER_POSTROUTING -s 127.0.0.11/32 -p udp -m udp --sport 53633 -j SNAT --to-source :53

iptables -t nat -Fを実行すると、これらのルールも削除されてしまい、コンテナ内からDNS解決ができなくなります。

解決策の実装

最終的な修正内容

DockerのDNS NATルールを保護する機能を追加しました:


DOCKER_DNS_RULES=$(iptables-save -t nat | grep "127\.0\.0\.11" || true)


iptables -t nat -F
...


if [ -n "$DOCKER_DNS_RULES" ]; then
    echo "Restoring Docker DNS rules..."
    iptables -t nat -N DOCKER_OUTPUT 2>/dev/null || true
    iptables -t nat -N DOCKER_POSTROUTING 2>/dev/null || true
    echo "$DOCKER_DNS_RULES" | xargs -L 1 iptables -t nat
else
    echo "No Docker DNS rules to restore"
fi

PRがマージされるまで

1. 下調べ

PRを出すと言っても、むやみやたらに出してもmergeされないと思いました。新卒研修時に、技術にはその技術の思想があるという話を思い出して、ClaudeCodeのPJの思想に沿ったものでないとコントリビュートできないと思い、以下のことを実行しました。

  • CONTRIBUTING.mdなどのcontributeに対しての詳細な説明・ルールがないか確認
  • 過去のPRやissueを確認して、「自分がやろうとしている修正は、既に他の誰かが着手していないか?、却下されていないか」「その上でどんなコメントがあるか」などを確認

調べたところ、数ヶ月前にiptableに関しての議論があったPRも存在したのですが、途中で終わっており、PRタブのところに”もしissueを修正したらPR出してね”って書いていたのでせっかくなら提出してみることにしました。

2. 検証

世界中で使われているClaude Codeに対してPRを出すのが恐れ多く、検証をちゃんとしてから出そうと思い、以下の手順で検証しました。

  1. テスト用のコンテナ環境を作成

    docker-compose.yml
     ```yaml
     services:
       test-workspace:
         build:
           context: .
           dockerfile: Dockerfile
         privileged: true  # Required for iptables
         volumes:
           - .:/workspace
           - /var/run/docker.sock:/var/run/docker.sock
         working_dir: /workspace
         command: sleep infinity
         networks:
           - test-network
         depends_on:
           - redis
     
       redis:
         image: redis:7-alpine
         networks:
           - test-network
     
     networks:
       test-network:
         driver: bridge
     ```
    
  2. 修正前と修正後で名前解決のテストを実行し、どのようにnatルールが変わるかで出力できるようなシェルスクリプトを作成

    run-dns-tests.sh (修正前・修正後・レビュー案の3パターンでDNS解決の動作を比較します)
     ```bash
     #!/bin/bash
     set -euo pipefail
     
     # Runner script for DNS firewall tests
     SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
     
     # Function to test original script in clean environment
     test_original_script() {
         echo "🧪 Testing ORIGINAL script in clean environment..."
         cd "$SCRIPT_DIR"
         
         # Start fresh environment
         docker compose up -d --build
         sleep 10
         
         echo "📝 Running ORIGINAL firewall script..."
         docker compose exec -T --user root test-workspace bash -c "
             echo '=== ORIGINAL SCRIPT TEST ===' > /tmp/original-test.log
             echo 'Testing original init-firewall.sh' >> /tmp/original-test.log
             echo >> /tmp/original-test.log
             
             echo 'Before script execution:' >> /tmp/original-test.log
             echo 'Docker DNS test:' >> /tmp/original-test.log
             timeout 5 dig @127.0.0.11 +short google.com >> /tmp/original-test.log 2>&1 || echo 'Docker DNS failed' >> /tmp/original-test.log
             echo >> /tmp/original-test.log
             
             echo 'Executing original script...' >> /tmp/original-test.log
             timeout 60 bash /workspace/init-firewall.sh >> /tmp/original-test.log 2>&1 || echo 'Script execution failed' >> /tmp/original-test.log
             echo >> /tmp/original-test.log
             
             echo 'After script execution:' >> /tmp/original-test.log
             echo 'iptables policies:' >> /tmp/original-test.log
             iptables -L | grep policy >> /tmp/original-test.log 2>&1
             echo 'Docker DNS test:' >> /tmp/original-test.log
             timeout 5 dig @127.0.0.11 +short google.com >> /tmp/original-test.log 2>&1 || echo 'Docker DNS failed' >> /tmp/original-test.log
             
             cat /tmp/original-test.log
         "
         
         # Copy results and cleanup
         docker compose exec -T test-workspace cat /tmp/original-test.log > original-test-results.log
         docker compose down
         echo "✅ ORIGINAL test completed!"
     }
     
     # Function to test modified script in clean environment  
     test_modified_script() {
         echo "🧪 Testing MODIFIED script in clean environment..."
         cd "$SCRIPT_DIR"
         
         # Start fresh environment
         docker compose up -d --build
         sleep 10
         
         echo "📝 Running MODIFIED firewall script..."
         docker compose exec -T --user root test-workspace bash -c "
             echo '=== MODIFIED SCRIPT TEST ===' > /tmp/modified-test.log
             echo 'Testing modified init-firewall-modified.sh' >> /tmp/modified-test.log
             echo >> /tmp/modified-test.log
             
             echo 'Before script execution:' >> /tmp/modified-test.log
             echo 'Docker DNS test:' >> /tmp/modified-test.log
             timeout 5 dig @127.0.0.11 +short google.com >> /tmp/modified-test.log 2>&1 || echo 'Docker DNS failed' >> /tmp/modified-test.log
             echo >> /tmp/modified-test.log
             
             echo 'Executing modified script...' >> /tmp/modified-test.log
             timeout 60 bash /workspace/init-firewall-modified.sh >> /tmp/modified-test.log 2>&1 || echo 'Script execution failed' >> /tmp/modified-test.log
             echo >> /tmp/modified-test.log
             
             echo 'After script execution:' >> /tmp/modified-test.log
             echo 'iptables policies:' >> /tmp/modified-test.log
             iptables -L | grep policy >> /tmp/modified-test.log 2>&1
             echo 'Docker DNS test:' >> /tmp/modified-test.log
             timeout 5 dig @127.0.0.11 +short google.com >> /tmp/modified-test.log 2>&1 || echo 'Docker DNS failed' >> /tmp/modified-test.log
             
             cat /tmp/modified-test.log
         "
         
         # Copy results and cleanup
         docker compose exec -T test-workspace cat /tmp/modified-test.log > modified-test-results.log
         docker compose down  
         echo "✅ MODIFIED test completed!"
     }
     
     # Function to test reviewer script in clean environment  
     test_reviewer_script() {
         echo "🧪 Testing REVIEWER script in clean environment..."
         cd "$SCRIPT_DIR"
         
         # Start fresh environment
         docker compose up -d --build
         sleep 10
         
         echo "📝 Running REVIEWER firewall script..."
         docker compose exec -T --user root test-workspace bash -c "
             echo '=== REVIEWER SCRIPT TEST ===' > /tmp/reviewer-test.log
             echo 'Testing reviewer init-firewall-reviewer.sh' >> /tmp/reviewer-test.log
             echo >> /tmp/reviewer-test.log
             
             echo 'Before script execution:' >> /tmp/reviewer-test.log
             echo 'Docker DNS test:' >> /tmp/reviewer-test.log
             timeout 5 dig @127.0.0.11 +short google.com >> /tmp/reviewer-test.log 2>&1 || echo 'Docker DNS failed' >> /tmp/reviewer-test.log
             echo >> /tmp/reviewer-test.log
             
             echo 'Executing reviewer script...' >> /tmp/reviewer-test.log
             timeout 60 bash /workspace/init-firewall-reviewer.sh >> /tmp/reviewer-test.log 2>&1 || echo 'Script execution failed' >> /tmp/reviewer-test.log
             echo >> /tmp/reviewer-test.log
             
             echo 'After script execution:' >> /tmp/reviewer-test.log
             echo 'iptables policies:' >> /tmp/reviewer-test.log
             iptables -L | grep policy >> /tmp/reviewer-test.log 2>&1
             echo 'Docker DNS test:' >> /tmp/reviewer-test.log
             timeout 5 dig @127.0.0.11 +short google.com >> /tmp/reviewer-test.log 2>&1 || echo 'Docker DNS failed' >> /tmp/reviewer-test.log
             
             cat /tmp/reviewer-test.log
         "
         
         # Copy results and cleanup
         docker compose exec -T test-workspace cat /tmp/reviewer-test.log > reviewer-test-results.log
         docker compose down  
         echo "✅ REVIEWER test completed!"
     }
     
     # Function to run all three tests and compare
     run_tests_in_container() {
         echo "🚀 Starting clean environment comparison tests..."
         
         # Test original script
         test_original_script
         echo ""
         
         # Test modified script  
         test_modified_script
         echo ""
         
         # Test reviewer script
         test_reviewer_script
         echo ""
         
         # Compare results
         echo "📊 Comparison Results:"
         echo "====================="
         echo ""
         echo "ORIGINAL Script Results:"
         echo "------------------------"
         cat original-test-results.log
         echo ""
         echo "MODIFIED Script Results:"  
         echo "------------------------"
         cat modified-test-results.log
         echo ""
         echo "REVIEWER Script Results:"
         echo "------------------------"
         cat reviewer-test-results.log
         
         echo ""
         echo "✅ All three tests completed! Check *-test-results.log files for details"
     }
     
     # Main execution
     case "${1:-run}" in
         "run")
             run_tests_in_container
             ;;
     esac
     ```
    
  3. 実際に検証し、修正前は名前解決ができず、自分のコードだと名前解決が問題なくできることを確認しました。

3. PR提出からマージまで

ClaudeCodeにPRを書かせて提出しました笑

https://github.com/anthropics/claude-code/pull/4644


TCP_PORT=$(iptables -t nat -L DOCKER_OUTPUT -n 2>/dev/null | grep 'tcp.*to:127.0.0.11:' | sed 's/.*127\.0\.0\.11://g' | cut -d' ' -f1 || echo "")
UDP_PORT=$(iptables -t nat -L DOCKER_OUTPUT -n 2>/dev/null | grep 'udp.*to:127.0.0.11:' | sed 's/.*127\.0\.0\.11://g' | cut -d' ' -f1 || echo "")
...
iptables -t nat -F
...

if [ -n "$TCP_PORT" ] && [ -n "$UDP_PORT" ]; then
    echo "Restoring Docker DNS with TCP:$TCP_PORT, UDP:$UDP_PORT"
    iptables -t nat -N DOCKER_OUTPUT
    iptables -t nat -N DOCKER_POSTROUTING
    iptables -t nat -A OUTPUT -d 127.0.0.11/32 -j DOCKER_OUTPUT
    iptables -t nat -A POSTROUTING -d 127.0.0.11/32 -j DOCKER_POSTROUTING
    iptables -t nat -A DOCKER_OUTPUT -d 127.0.0.11/32 -p tcp -j DNAT --to-destination 127.0.0.11:$TCP_PORT
    iptables -t nat -A DOCKER_OUTPUT -d 127.0.0.11/32 -p udp -j DNAT --to-destination 127.0.0.11:$UDP_PORT
    iptables -t nat -A DOCKER_POSTROUTING -s 127.0.0.11/32 -p tcp -j SNAT --to-source :53
    iptables -t nat -A DOCKER_POSTROUTING -s 127.0.0.11/32 -p udp -j SNAT --to-source :53
fi

以上のように

  • TCPとUDPのポートがエフェメラルポートなので先に取得しておく
  • そのあと、各行を挿入していく

PR出してみると

@shota-0129 thanks for opening this PR. How would you feel about something like this?

1. Extract Docker DNS info BEFORE any flushing
DOCKER_DNS_RULES=$(iptables-save -t nat | grep "127\.0\.0\.11")

2. Perform security lockdown (flush + rebuild allowlist)
iptables -t nat -F
... rebuild strict allowlist rules ...

3. Selectively restore ONLY internal Docker DNS resolution
if [ -n "$DOCKER_DNS_RULES" ]; then
    echo "$DOCKER_DNS_RULES" | iptables-restore --noflush
fi 

とより簡潔な表現できない?というフィードバックが飛んできました。

フィードバックが飛んでくる=即時却下ではないと思ったので、大変嬉しかったです。
フィードバックの内容を含めて検証し、

  • ルールを追加する前にチェーンを作成する
  • iptables-restoreでなくxargs を使用して各行を保存すること
if [ -n "$DOCKER_DNS_RULES" ]; then
    echo "Restoring Docker DNS rules..."
    iptables -t nat -N DOCKER_OUTPUT 2>/dev/null || true
    iptables -t nat -N DOCKER_POSTROUTING 2>/dev/null || true
    echo "$DOCKER_DNS_RULES" | xargs -L 1 iptables -t nat
else
    echo "No Docker DNS rules to restore"
fi

に修正し再度pushしたところ、少し修正される形でマージされました🎉
マージされたメールが嬉しくて目が覚めて、いつもより会社に早く行けました笑

学んだこと「3つの大切さ」

1. 基礎知識

新卒研修で学んだ内容

  • Dockerのネットワーク: ブリッジ、NAT、内部DNS
  • DNS: 名前解決の流れ

これらが全部繋がって、問題を解決できました。
インフラ・コンテナ研修をしていただいた先輩方ありがとうございました🙇

2. 公式ドキュメント・コードを読むこと

まず、今回の問題発見は、ClaudeCodeの公式ドキュメントを読みながら作成し、なおかつその中で公式のコードを紐解いて問題を発見しました。
学生の頃は、よくネット記事で同じ事象を探すことばっかりしていましたが、自分のエラーがどこにあるのかを実際のコードを読んで理解する重要さを学びました。

3. 挑戦すること

弊社で大事にしている3つの要素の中に「挑戦」というのがあるのですが、今回はその「挑戦」だったなと思います。
自分は学生の頃、優秀なエンジニアではなくて、入社した同期とも比べると未熟な部分が多いなと思う日々ですが、それでも今回、自分の中で研修中に成長したことを糧に挑戦できたこと、そしてその結果、コントリビュートできて自分の中で1つ誇りに思えることができました。
挑戦したからこそ、こんな記事も書けているので1つの成功体験として大事にしながら、これからも挑戦したいと思います💪

まとめ

Claude Codeへの初めてのコントリビュートは、自分にとって大きな自信になりました。

  • 基礎知識の積み重ねが実際の問題解決に直結した
  • 挑戦したからこそ、自分にとっての成功体験が生まれた

これからも日々の学びを大切にしながら、エンジニアとして成長できればと思います。
そして、この記事を読んで「自分にもできるかも」「なんか挑戦しようかな」と思ってくれる新卒エンジニアが一人でも増えれば嬉しいです!

余談

新卒(25卒)のエンジニアで交流会を2025年秋頃に開こうかなと思っているので、もし興味ある方は以下のプロフィールのSNSから連絡ください〜


参考リンク



Source link

Views: 0

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -