Azure Storage 계정의 클라이언트 애플리케이션 오류 문제 해결

이 문서는 Azure Monitor에서 메트릭, 클라이언트 쪽 로그 및 리소스 로그를 사용하여 클라이언트 애플리케이션 오류를 조사하는 데 도움이 됩니다.

오류 진단

애플리케이션 사용자는 클라이언트 애플리케이션에서 보고한 오류를 알릴 수 있습니다. 또한 Azure Monitor는 NetworkError, ClientTimeoutError 또는 AuthorizationError와 같은 스토리지 서비스에서 다양한 응답 유형(ResponseType 차원)의 수를 기록합니다. Azure Monitor는 다양한 오류 유형의 개수만 기록하지만 서버 쪽, 클라이언트 쪽 및 네트워크 로그를 검사하여 개별 요청에 대한 자세한 정보를 얻을 수 있습니다. 일반적으로 스토리지 서비스에서 반환된 HTTP 상태 코드는 요청이 실패한 이유를 나타냅니다.

참고

몇 가지 일시적인 오류가 표시되어야 합니다. 예를 들어 일시적인 네트워크 조건 또는 애플리케이션 오류로 인한 오류입니다.

다음 리소스는 스토리지 관련 상태 및 오류 코드를 이해하는 데 유용합니다.

클라이언트가 HTTP 403(사용할 수 없음) 메시지를 수신하고 있습니다.

클라이언트 애플리케이션이 HTTP 403(사용할 수 없음) 오류를 throw하는 경우 클라이언트가 스토리지 요청을 보낼 때 만료된 SAS(공유 액세스 서명)를 사용하고 있는 것일 수 있습니다(다른 가능한 원인으로는 클록 기울이기, 잘못된 키 및 빈 헤더가 포함됨).

.NET용 스토리지 클라이언트 라이브러리를 사용하면 애플리케이션에서 수행하는 스토리지 작업과 관련된 클라이언트 쪽 로그 데이터를 수집할 수 있습니다. 자세한 내용은 .NET Storage 클라이언트 라이브러리를 사용하여 클라이언트 쪽 로깅을 참조하세요.

다음 표에서는 이 문제가 발생하는 것을 보여 주는 스토리지 클라이언트 라이브러리에서 생성된 클라이언트 쪽 로그의 샘플을 보여 줍니다.

원본 세부 정보 표시 세부 정보 표시 클라이언트 요청 ID 작업 텍스트
Microsoft.Azure.Storage 정보 3 85d077ab-... Starting operation with location Primary per location mode PrimaryOnly.
Microsoft.Azure.Storage 정보 3 85d077ab -... Starting synchronous request to <https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests#Synchronous_request>
Microsoft.Azure.Storage 정보 3 85d077ab -... Waiting for response.
Microsoft.Azure.Storage 경고 2 85d077ab -... Exception thrown while waiting for response: The remote server returned an error: (403) Forbidden.
Microsoft.Azure.Storage 정보 3 85d077ab -... Response received. Status code = 403, Request ID = <Request ID>, Content-MD5 = , ETag = .
Microsoft.Azure.Storage 경고 2 85d077ab -... Exception thrown during the operation: The remote server returned an error: (403) Forbidden..
Microsoft.Azure.Storage 정보 3 85d077ab -... Checking if the operation should be retried. Retry count = 0, HTTP status code = 403, Exception = The remote server returned an error: (403) Forbidden..
Microsoft.Azure.Storage 정보 3 85d077ab -... The next location has been set to Primary, based on the location mode.
Microsoft.Azure.Storage 오류 1 85d077ab -... Retry policy did not allow for a retry. Failing with The remote server returned an error: (403) Forbidden.

이 시나리오에서는 클라이언트가 서버에 토큰을 보내기 전에 SAS 토큰이 만료되는 이유를 조사해야 합니다.

  • 일반적으로 클라이언트에서 즉시 사용할 SAS를 만들 때 시작 시간을 설정해서는 안 됩니다. 현재 시간을 사용하여 SAS를 생성하는 호스트와 스토리지 서비스 간에 약간의 클록 차이가 있는 경우 스토리지 서비스에서 아직 유효하지 않은 SAS를 받을 수 있습니다.

  • SAS에서 매우 짧은 만료 시간을 설정하지 마세요. 다시 말하지만, SAS를 생성하는 호스트와 스토리지 서비스 간의 작은 클록 차이로 인해 SAS가 예상보다 일찍 만료될 수 있습니다.

  • SAS 키의 version 매개 변수(예 sv: =2015-04-05)가 사용 중인 Storage 클라이언트 라이브러리 버전과 일치하나요? 항상 최신 버전의 스토리지 클라이언트 라이브러리를 사용하는 것이 좋습니다.

  • 스토리지 액세스 키를 다시 생성하는 경우 기존 SAS 토큰이 무효화될 수 있습니다. 이 문제는 클라이언트 애플리케이션이 캐시할 만료 시간이 긴 SAS 토큰을 생성하는 경우에 발생할 수 있습니다.

스토리지 클라이언트 라이브러리를 사용하여 SAS 토큰을 생성하는 경우 유효한 토큰을 쉽게 빌드할 수 있습니다. 그러나 스토리지 REST API를 사용하고 SAS 토큰을 직접 생성하는 경우 공유 액세스 서명을 사용하여 액세스 위임을 참조하세요.

클라이언트가 HTTP 404(찾을 수 없음) 메시지를 수신하고 있습니다.

클라이언트 애플리케이션이 서버에서 HTTP 404(찾을 수 없음) 메시지를 수신하는 경우 이는 클라이언트가 사용하려는 개체(예: 엔터티, 테이블, Blob, 컨테이너 또는 큐)가 스토리지 서비스에 없음을 의미합니다. 다음과 같은 여러 가지 가능한 이유가 있습니다.

  • 클라이언트 또는 다른 프로세스가 이전에 개체를 삭제했습니다.

  • SAS(공유 액세스 서명) 권한 부여 문제입니다.

  • 클라이언트 쪽 JavaScript 코드에는 개체에 액세스할 수 있는 권한이 없습니다.

  • 네트워크 오류.

이전에 개체를 삭제한 클라이언트 또는 다른 프로세스

클라이언트가 스토리지 서비스에서 데이터를 읽거나 업데이트하거나 삭제하려고 하는 시나리오에서는 스토리지 서비스에서 문제의 개체를 삭제한 이전 작업을 스토리지 리소스 로그에서 쉽게 식별할 수 있습니다. 로그 데이터에 다른 사용자 또는 프로세스가 개체를 삭제한 것으로 표시되는 경우가 많습니다. 클라이언트가 개체를 삭제한 경우 Azure Monitor 로그(서버 쪽)가 표시됩니다.

클라이언트가 개체를 삽입하려고 하는 시나리오에서는 클라이언트가 새 개체를 만드는 경우 HTTP 404(찾을 수 없음) 응답이 발생하는 이유가 즉시 명확하지 않을 수 있습니다. 그러나 클라이언트가 Blob을 만드는 경우 Blob 컨테이너를 찾을 수 있어야 합니다. 클라이언트가 메시지를 만드는 경우 큐를 찾을 수 있어야 합니다. 클라이언트가 행을 추가하는 경우 테이블을 찾을 수 있어야 합니다.

스토리지 클라이언트 라이브러리의 클라이언트 쪽 로그를 사용하여 클라이언트가 스토리지 서비스에 특정 요청을 보내는 시기를 더 잘 이해할 수 있습니다.

Storage 클라이언트 라이브러리에서 생성된 다음 클라이언트 쪽 로그는 클라이언트가 만드는 Blob에 대한 컨테이너를 찾을 수 없는 경우의 문제를 보여 줍니다. 이 로그에는 다음 스토리지 작업에 대한 세부 정보가 포함됩니다.

요청 ID 작업
07b26a5d-... DeleteIfExists 메서드를 사용하여 Blob 컨테이너를 삭제합니다. 이 작업에는 컨테이너의 존재에 대한 검사 HEAD 요청이 포함됩니다.
e2d06d78... CreateIfNotExists 메서드를 사용하여 Blob 컨테이너를 만듭니다. 이 작업에는 HEAD 컨테이너의 존재를 확인하는 요청이 포함됩니다. 는 HEAD 404 메시지를 반환하지만 계속합니다.
de8b1c3c-... UploadFromStream 메서드를 사용하여 Blob을 만듭니다. 요청이 PUT 404 메시지와 함께 실패함

로그 항목:

요청 ID 작업 텍스트
07b26a5d-... Starting synchronous request to https://domemaildist.blob.core.windows.net/azuremmblobcontainer.
07b26a5d-... StringToSign = HEAD............x-ms-client-request-id:07b26a5d-....x-ms-date:Tue, 03 Jun 2014 10:33:11 GMT.x-ms-version:2014-02-14./domemaildist/azuremmblobcontainer.restype:container.
07b26a5d-... Waiting for response.
07b26a5d-... Response received. Status code = 200, Request ID = eeead849-...Content-MD5 = , ETag = &quot;0x8D14D2DC63D059B&quot;.
07b26a5d-... Response headers were processed successfully, proceeding with the rest of the operation.
07b26a5d-... Downloading response body.
07b26a5d-... Operation completed successfully.
07b26a5d-... Starting synchronous request to https://domemaildist.blob.core.windows.net/azuremmblobcontainer.
07b26a5d-... StringToSign = DELETE............x-ms-client-request-id:07b26a5d-....x-ms-date:Tue, 03 Jun 2014 10:33:12 GMT.x-ms-version:2014-02-14./domemaildist/azuremmblobcontainer.restype:container.
07b26a5d-... Waiting for response.
07b26a5d-... Response received. Status code = 202, Request ID = 6ab2a4cf-..., Content-MD5 = , ETag = .
07b26a5d-... Response headers were processed successfully, proceeding with the rest of the operation.
07b26a5d-... Downloading response body.
07b26a5d-... Operation completed successfully.
e2d06d78-... Starting asynchronous request to https://domemaildist.blob.core.windows.net/azuremmblobcontainer.
e2d06d78-... StringToSign = HEAD............x-ms-client-request-id:e2d06d78-....x-ms-date:Tue, 03 Jun 2014 10:33:12 GMT.x-ms-version:2014-02-14./domemaildist/azuremmblobcontainer.restype:container.
e2d06d78-... Waiting for response.
de8b1c3c-... Starting synchronous request to https://domemaildist.blob.core.windows.net/azuremmblobcontainer/blobCreated.txt.
de8b1c3c-... StringToSign = PUT...64.qCmF+TQLPhq/YYK50mP9ZQ==........x-ms-blob-type:BlockBlob.x-ms-client-request-id:de8b1c3c-....x-ms-date:Tue, 03 Jun 2014 10:33:12 GMT.x-ms-version:2014-02-14./domemaildist/azuremmblobcontainer/blobCreated.txt.
de8b1c3c-... Preparing to write request data.
e2d06d78-... Exception thrown while waiting for response: The remote server returned an error: (404) Not Found..
e2d06d78-... Response received. Status code = 404, Request ID = 353ae3bc-..., Content-MD5 = , ETag = .
e2d06d78-... Response headers were processed successfully, proceeding with the rest of the operation.
e2d06d78-... Downloading response body.
e2d06d78-... Operation completed successfully.
e2d06d78-... Starting asynchronous request to https://domemaildist.blob.core.windows.net/azuremmblobcontainer.
e2d06d78-... StringToSign = PUT...0.........x-ms-client-request-id:e2d06d78-....x-ms-date:Tue, 03 Jun 2014 10:33:12 GMT.x-ms-version:2014-02-14./domemaildist/azuremmblobcontainer.restype:container.
e2d06d78-... Waiting for response.
de8b1c3c-... Writing request data.
de8b1c3c-... Waiting for response.
e2d06d78-... Exception thrown while waiting for response: The remote server returned an error: (409) Conflict..
e2d06d78-... Response received. Status code = 409, Request ID = c27da20e-..., Content-MD5 = , ETag = .
e2d06d78-... Downloading error response body.
de8b1c3c-... Exception thrown while waiting for response: The remote server returned an error: (404) Not Found..
de8b1c3c-... Response received. Status code = 404, Request ID = 0eaeab3e-..., Content-MD5 = , ETag = .
de8b1c3c-... Exception thrown during the operation: The remote server returned an error: (404) Not Found..
de8b1c3c-... Retry policy did not allow for a retry. Failing with The remote server returned an error: (404) Not Found..
e2d06d78-... Retry policy did not allow for a retry. Failing with The remote server returned an error: (409) Conflict..

이 예제에서 로그는 클라이언트가 메서드의 요청(요청 CreateIfNotExists ID e2d06d78...)과 메서드의 요청 UploadFromStream (de8b1c3c-...)을 인터리빙하고 있음을 보여 줍니다. 이 인터리빙은 클라이언트 애플리케이션이 이러한 메서드를 비동기적으로 호출하기 때문에 발생합니다. 클라이언트에서 비동기 코드를 수정하여 해당 컨테이너의 Blob에 데이터를 업로드하기 전에 컨테이너를 만들도록 합니다. 이상적으로는 모든 컨테이너를 미리 만들어야 합니다.

SAS(공유 액세스 서명) 권한 부여 문제

클라이언트 애플리케이션이 작업에 필요한 권한을 포함하지 않는 SAS 키를 사용하려고 하면 스토리지 서비스는 HTTP 404(찾을 수 없음) 메시지를 클라이언트에 반환합니다. 동시에 Azure Monitor 메트릭에는 ResponseType 차원에 대한 AuthorizationError도 표시됩니다.

클라이언트 애플리케이션이 권한이 부여되지 않은 작업을 수행하려는 이유를 조사합니다.

클라이언트 쪽 JavaScript 코드에는 개체에 액세스할 수 있는 권한이 없습니다.

JavaScript 클라이언트를 사용하는 경우 스토리지 서비스에서 HTTP 404 메시지를 반환하는 경우 브라우저에서 다음 JavaScript 오류에 대해 검사.

SEC7120: Access-Control-Allow-Origin 헤더에서 원본 http://localhost:56309 을 찾을 수 없습니다.
SCRIPT7002: XMLHttpRequest: 네트워크 오류 0x80070005 액세스가 거부되었습니다.

참고

인터넷 Explorer F12 개발자 도구를 사용하여 클라이언트 쪽 JavaScript 문제를 해결할 때 브라우저와 스토리지 서비스 간에 교환된 메시지를 추적할 수 있습니다.

이러한 오류는 웹 브라우저가 웹 페이지가 페이지가 가져온 도메인과 다른 도메인의 API를 호출하지 못하도록 하는 동일한 원본 정책 보안 제한을 구현하기 때문에 발생합니다.

JavaScript 문제를 해결하려면 클라이언트가 액세스하는 스토리지 서비스에 대해 CORS(원본 간 리소스 공유)를 구성할 수 있습니다. 자세한 내용은 Azure Storage 서비스에 대한 CORS(원본 간 리소스 공유) 지원을 참조하세요.

다음 코드 샘플에서는 Contoso 도메인에서 실행되는 JavaScript가 Blob Storage 서비스의 Blob에 액세스할 수 있도록 Blob 서비스를 구성하는 방법을 보여 줍니다.

var connectionString = Constants.connectionString;

 BlobServiceClient blobServiceClient = new BlobServiceClient(connectionString);

 BlobServiceProperties sp = blobServiceClient.GetProperties();

 // Set the service properties.
 sp.DefaultServiceVersion = "2013-08-15";
 BlobCorsRule bcr = new BlobCorsRule();
 bcr.AllowedHeaders = "*";

 bcr.AllowedMethods = "GET,POST";
 bcr.AllowedOrigins = "http://www.contoso.com";
 bcr.ExposedHeaders = "x-ms-*";
 bcr.MaxAgeInSeconds = 5;
 sp.Cors.Clear();
 sp.Cors.Add(bcr);
 blobServiceClient.SetProperties(sp);

네트워크 오류

경우에 따라 네트워크 패킷이 손실되면 스토리지 서비스가 HTTP 404 메시지를 클라이언트로 반환할 수 있습니다. 예를 들어 클라이언트 애플리케이션이 테이블 서비스에서 엔터티를 삭제하는 경우 클라이언트가 테이블 서비스에서 "HTTP 404(찾을 수 없음)" 상태 메시지를 보고하는 스토리지 예외를 throw하는 것을 볼 수 있습니다. 테이블 스토리지 서비스의 테이블을 조사할 때 서비스가 요청된 대로 엔터티를 삭제한 것을 볼 수 있습니다.

클라이언트의 예외 세부 정보에는 요청에 대한 테이블 서비스에서 할당한 요청 ID(7e84f12d...)가 포함됩니다. 이 정보를 사용하여 로그 항목의 작업이 인증된 방법을 설명하는 필드를 검색하여 Azure Monitor의 스토리지 리소스 로그에서 요청 세부 정보를 찾을 수 있습니다. 메트릭을 사용하여 이와 같은 오류가 발생하는 시기를 식별한 다음 메트릭이 이 오류를 기록한 시간에 따라 로그 파일을 검색할 수도 있습니다. 이 로그 항목은 "HTTP(404) 클라이언트 기타 오류" 상태 메시지와 함께 삭제가 실패했음을 보여 줍니다. 동일한 로그 항목에는 열(813ea74f...)에서 클라이언트가 client-request-id 생성한 요청 ID도 포함됩니다.

또한 서버 쪽 로그에는 동일한 엔터티 및 동일한 client-request-id 클라이언트에서 성공적으로 삭제 작업을 수행할 수 있는 동일한 값(813ea74f...)의 다른 항목도 포함됩니다. 이 성공적인 삭제 작업은 실패한 삭제 요청 직전에 수행되었습니다.

이 시나리오의 가장 가능성이 높은 원인은 클라이언트가 엔터티에 대한 삭제 요청을 테이블 서비스로 보냈기 때문입니다. 이 요청은 성공했지만 서버에서 승인을 받지 못했습니다(아마도 임시 네트워크 문제로 인해). 그런 다음 클라이언트는 동일한 client-request-id를 사용하여 작업을 자동으로 다시 시도했으며 엔터티가 이미 삭제되었기 때문에 이 재시도에 실패했습니다.

이 문제가 자주 발생하는 경우 클라이언트가 테이블 서비스에서 승인을 받지 못하는 이유를 조사해야 합니다. 문제가 일시적인 경우 "HTTP(404) 찾을 수 없음" 오류를 트래핑하고 클라이언트에 기록해야 하지만 클라이언트가 계속되도록 허용해야 합니다.

클라이언트가 HTTP 409(충돌) 메시지를 수신하고 있습니다.

클라이언트가 Blob 컨테이너, 테이블 또는 큐를 삭제하는 경우 이름을 다시 사용할 수 있게 되기까지 짧은 기간이 있습니다. 클라이언트 애플리케이션의 코드가 삭제된 후 동일한 이름을 사용하여 Blob 컨테이너를 즉시 다시 만드는 경우 메서드는 CreateIfNotExists 결국 HTTP 409(충돌) 오류와 함께 실패합니다.

클라이언트 애플리케이션은 삭제/다시 만들기 패턴이 일반적인 경우 새 컨테이너를 만들 때마다 고유한 컨테이너 이름을 사용해야 합니다.

메트릭에 낮은 PercentSuccess 또는 분석 로그 항목에 ClientOtherErrors의 트랜잭션 상태 있는 작업이 있음을 보여 줍니다.

Success 값과 동일한 ResponseType 차원은 HTTP 상태 코드를 기반으로 성공한 작업의 백분율을 캡처합니다. 상태 코드가 2XX인 작업은 성공으로 계산되는 반면, 3XX, 4XX 및 5XX 범위의 상태 코드를 사용하는 작업은 실패한 것으로 계산되고 성공 메트릭 값은 낮아집니다. 스토리지 리소스 로그에서 이러한 작업은 ClientOtherError의 트랜잭션 상태 기록됩니다.

이러한 작업이 성공적으로 완료되었으므로 가용성과 같은 다른 메트릭에는 영향을 주지 않습니다. 성공적으로 실행되지만 실패한 HTTP 상태 코드가 발생할 수 있는 작업의 몇 가지 예는 다음과 같습니다.

  • ResourceNotFound (찾을 수 없음 404) 예를 들어 GET 요청에서 존재하지 않는 Blob에 이르기까지.
  • ResourceAlreadyExists (충돌 409) 예를 들어 리소스가 CreateIfNotExist 이미 있는 작업입니다.
  • ConditionNotMet(수정되지 않음 304) 예를 들어 클라이언트가 마지막 작업 이후 업데이트된 경우에만 이미지를 요청하기 위해 값 및 HTTP If-None-Match 헤더를 보내는 ETag 경우와 같은 조건부 작업에서 발생합니다.

스토리지 서비스가 반환하는 일반적인 REST API 오류 코드 목록은 일반 REST API 오류 코드 페이지에서 찾을 수 있습니다.

참고 항목

도움을 요청하십시오.

질문이 있거나 도움이 필요한 경우 지원 요청을 생성하거나Azure 커뮤니티 지원에 문의하세요. Azure 피드백 커뮤니티에 제품 피드백을 제출할 수도 있습니다.