我有 3 個 docker 容器在 mac os 中運行。
- 后端 - 埠 5055
- 前端 (next.js) - 埠 3000
- nginx - 埠 80
當我從瀏覽器 (http://localhost:80) 訪問時,我收到后端 api 請求的net::ERR_FAILED。我可以在郵遞員中向后端(http://localhost:5055)發出請求并且效果很好。
示例 api 請求 - GET http://backend:5055/api/category/show

這種行為的原因是什么?
謝謝。
docker-compose.yml
version: '3.9'
services:
backend:
image: backend-image
build:
context: ./backend
dockerfile: Dockerfile.prod
ports:
- '5055:5055'
frontend:
image: frontend-image
build:
context: ./frontend
dockerfile: Dockerfile.prod
ports:
- '3000:3000'
depends_on:
- backend
nginx:
image: nginx-image
build:
context: ./nginx
ports:
- '80:80'
depends_on:
- backend
- frontend
后端 - Dockerfile.prod
FROM node:19.0.1-slim
WORKDIR /app
COPY package.json .
RUN yarn install
COPY . ./
ENV PORT 5055
EXPOSE $PORT
CMD ["npm", "run", "start"]
前端 - Dockerfile.prod
FROM node:19-alpine
WORKDIR /usr/app
RUN npm install --global pm2
COPY ./package*.json ./
RUN npm install
COPY ./ ./
RUN npm run build
EXPOSE 3000
USER node
CMD [ "pm2-runtime", "start", "npm", "--", "start" ]
nginx-Dockerfile
FROM public.ecr.aws/nginx/nginx:stable-alpine
RUN rm /etc/nginx/conf.d/*
COPY ./default.conf /etc/nginx/conf.d/
EXPOSE 80
CMD [ "nginx", "-g", "daemon off;" ]
nginx-default.conf
upstream frontend {
server frontend:3000;
}
upstream backend {
server backend:5055;
}
server {
listen 80 default_server;
...
location /api {
...
proxy_pass http://backend;
proxy_redirect off;
...
}
location /_next/static {
proxy_cache STATIC;
proxy_pass http://frontend;
}
location /static {
proxy_cache STATIC;
proxy_ignore_headers Cache-Control;
proxy_cache_valid 60m;
proxy_pass http://frontend;
}
location / {
proxy_pass http://frontend;
}
}
前端 - .env.local
NEXT_PUBLIC_API_BASE_URL=http://backend:5055/api
前端 - httpServices.js
import axios from 'axios'
import Cookies from 'js-cookie'
const instance = axios.create({
baseURL: `${process.env.NEXT_PUBLIC_API_BASE_URL}`,
timeout: 500000,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
})
...
const responseBody = (response) => response.data
const requests = {
get: (url, body) => instance.get(url, body).then(responseBody),
post: (url, body, headers) =>
instance.post(url, body, headers).then(responseBody),
put: (url, body) => instance.put(url, body).then(responseBody),
}
export default requests
編輯
- nginx 日志 (docker logs -f nginx 2>/dev/null)
172.20.0.1 - - [14/Nov/2022:17:02:39 0000] "GET /_next/image?url=/slider/slider-1.jpg&w=1080&q=75 HTTP/1.1" 304 0 "http://localhost/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" "-"
172.20.0.1 - - [14/Nov/2022:17:02:41 0000] "GET /service-worker.js HTTP/1.1" 304 0 "http://localhost/service-worker.js" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" "-"
172.20.0.1 - - [14/Nov/2022:17:02:41 0000] "GET /fallback-B639VDPLP_r91l2hRR104.js HTTP/1.1" 304 0 "http://localhost/service-worker.js" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" "-"
172.20.0.1 - - [14/Nov/2022:17:02:41 0000] "GET /workbox-fbc529db.js HTTP/1.1" 304 0 "http://localhost/service-worker.js" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" "-"
- curl 請求從 nginx 容器到后端容器運行良好(curl 后端:5055/api/category/show)
編輯 2
const CategoryCarousel = () => {
...
const { data, error } = useAsync(() => CategoryServices.getShowingCategory())
...
}
import requests from './httpServices'
const CategoryServices = {
getShowingCategory() {
return requests.get('/category/show')
},
}
編輯 3
當 NEXT_PUBLIC_API_BASE_URL=http://localhost:5055/api
Error: connect ECONNREFUSED 127.0.0.1:5055
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1283:16) {
errno: -111,
code: 'ECONNREFUSED',
syscall: 'connect',
address: '127.0.0.1',
port: 5055,
config: {
...
baseURL: 'http://localhost:5055/api',
method: 'get',
url: '/products/show',
data: undefined
},
...
_options: {
...
protocol: 'http:',
path: '/api/products/show',
method: 'GET',
...
pathname: '/api/products/show'
},
...
},
_currentUrl: 'http://localhost:5055/api/products/show',
_timeout: null,
},
response: undefined,
isAxiosError: true,
}
uj5u.com熱心網友回復:
Docker配置全部正確
我再次閱讀了這個問題,使用給定的日志,您的配置似乎一直都是正確的。但是,您在瀏覽器上所做的是訪問沖突。
Docker 組合服務(主機)訪問
Docker 服務相互連接,這意味著可以在另一個服務中向另一個服務發出請求。不可能的是該服務之外的用戶無法訪問其他服務。這是一個更簡單的解釋:
# docker-compose.yaml
service-a:
ports:
- 3000:3000 # exposed to outside
# ...
service-b:
ports:
- 5000:5000 # exposed to outside
# ...
# ?this works! case A
(service-a) $ curl http://service-b:5000
(service-b) $ curl http://service-a:3000
(local machine) $ curl http://localhost:5000
(local machine) $ curl http://localhost:3000
# ?this does not work! case B
(local machine) $ curl http://service-a:3000
(local machine) $ curl http://service-b:3000
當我最初閱讀這個問題時,我錯過了前端代碼正在訪問瀏覽器中未公開的后端服務的部分。這顯然屬于情況B,這是行不通的。

解決方案:使用服務器端渲染...
前端應用程式只是您瀏覽器上的一段 JavaScript 代碼。它無法訪問內部。因此,應更正請求:
# change from http://backend:5055/api
NEXT_PUBLIC_API_BASE_URL=http://localhost:5055/api
但這是解決它的更好方法。嘗試訪問服務器端代碼中的 api 。由于您的前端是 Next.js,因此可以將后端結果注入前端。
export async function getServerSideProps(context) {
const req = await fetch('http://backend:5055/api/...');
const data = req.json();
return {
props: { data }, // will be passed to the page component as props
}
}
編輯
當 NEXT_PUBLIC_API_BASE_URL 更改為“localhost”時(編輯 3)包含前端日志(如您在答案中所述)。現在錯誤來自不同的 api,即 getServerSideProps() 內的“localhost:5055/api/products/show”。發生這種情況是因為某些 api 是從客戶端呼叫的,而有些是從服務器端呼叫的嗎?如果是這種情況,我應該如何解決這個問題?謝謝
這是更實際的例子:
// outside getServerSideProps, getStaticProps - browser
// ?this will fail 1-a
await fetch('http://backend:5055/...') // 'backend' should be service name
// ?this will work 1-b
await fetch('http://localhost:5055/...')
// inside getServerSideProps or getStaticProps - internal network
export const getServerSideProps() {
// ?this will work 2-a
await fetch('http://backend:5055/...');
// ?this will fail 2-b
await fetch('http://localhost:5055/...');
}
簡而言之,請求必須是 1-b 或 2-a。
發生這種情況是因為某些 api 是從客戶端呼叫的,而有些是從服務器端呼叫的嗎?如果是這種情況,我應該如何解決這個問題?謝謝
是的。有幾種方法可以處理它。
1.以編程方式區分主機
const isServer = typeof window === 'undefined';
const HOST_URL = isServer ? 'http://backend:5055' : 'http://localhost:5055';
fetch(HOST_URL);
2.手動區分主機
// server side
getServerSideProps() {
// this seems unnecessary and confusing at first sight but it comes very handy later in terms of security.
fetch('http://backend:5055');
}
// client side
fetch('http://localhost:5055');
3.后端使用獨立域(修改hosts檔案)
這是我在本地環境中使用域名測驗服務時通常采用的方法。
修改主機檔案,意味著exampleurl.com將localhost在作業系統中決議。在這種情況下,生產環境必須使用單獨的域并且需要主機檔案設定。服務必須公開。請參考這篇關于修改hosts檔案的檔案。
# docker-compose.yaml
services:
backend:
ports:
- 5050:5050
# ...
# hosts file
127.0.0.1 exampleurl.com
# ...
// in this case,
// development == local development
const IS_DEV = process.NODE_ENV = 'development';
// hosts file does not resolve port. it's necessary for local development.
const BACKEND_HOST = 'exampleurl.com'
const BACKEND_URL = IS_DEV ? `${BACKEND_HOST}:5050` : BACKEND_HOST;
// client-side
fetch(BACKEND_URL);
// server-side
getServerSideProps() {
fetch(BACKEND_URL);
}
有許多聰明的方法可以解決這個問題,但沒有“永遠正確”的答案。花點時間考慮哪種方法最適合您的情況。
uj5u.com熱心網友回復:
在 nginx.conf 中,您還需要指定后端埠以及基本路徑 (/api):
location /api {
...
proxy_pass http://backend:5055/api;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/535060.html
