docs(TechLog): 주제 7~10 을 다시 쓴다
주소가 게시 시점에 굳어 저장되는 구조, 축 링크를 두 번 옮긴 순서, 한글 slug 가 간헐적으로 보인 두 가지 어긋남을 표로 갈랐다. 화면이 실패를 없음으로 그릴 때 작성 도구에서 왜 더 오래 숨는지, Promise.all 이 거절과 던짐에서 다른 경로를 타는 이유를 채웠다. CSS module 이 왜 전역 규칙에 닿지 않는지, 403 과 404 가 원인을 어떻게 좁혔는지도 적었다. link-audit.py 를 감사 Case 의 evidence 로 걸어 배정한 증거 하나를 메웠다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
193da20d09
commit
b1653dbba8
+12
-4
@@ -60,23 +60,31 @@ tech-log-design-package : 71bab4c · b93d62a
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
## 앵커로 옮겼더니 자기 자신을 가리켰다
|
||||
## 주소만 바뀌고 화면은 그대로였다
|
||||
|
||||
주제 화면의 네 줄은 링크로 그려져 있었는데 눌러도 아무 일이 없었다.
|
||||
|
||||
처음에 축의 주소를 `/topics/{주제}/{축}` 이라 적어 두었는데 그런 화면이 없었다. 그래서 축의 주소를 주제 화면 안의 앵커로 바꿨다.
|
||||
|
||||
주제 화면에서 그 링크를 누르면 주소에 앵커가 붙는다. 화면은 이미 그 주제 화면이므로 아무것도 바뀌지 않는다.
|
||||
주제 화면에서 그 링크를 누르면 주소에 앵커가 붙는다. 화면은 이미 그 주제 화면이므로 라우터가 다시 그릴 것이 없고, 앵커가 가리키는 element 도 없으니 스크롤도 일어나지 않는다.
|
||||
|
||||
## 축에 자기 화면을 줬다
|
||||
|
||||
목록 조회에 축 필터를 더하고 `record_variant` 로 거른다. 축 slug 는 주제 안에서만 유일하므로 주제까지 맞춰야 하고, 주제를 빼면 다른 주제의 같은 이름 축까지 걸린다.
|
||||
목록 조회에 축 필터를 더하고 기록이 어느 축에 걸리는지를 담은 표로 거른다.
|
||||
|
||||
축 slug 는 주제 안에서만 유일하므로 조회에서 주제까지 함께 맞춘다. 주제를 빼면 다른 주제의 같은 이름 축이 함께 걸린다 — 인증 경계 주제의 `bff` 와 다른 주제의 `bff` 가 구분되지 않는다.
|
||||
|
||||
## 배포 순서로 만든 2차 사고
|
||||
|
||||
> **이 건에서 제가 만든 2차 사고:** 축 화면을 만들고 **백엔드를 프론트보다 먼저 배포했습니다.** nginx 설정은 라우트 계약에서 생성되므로, 프론트가 배포되기 전까지 `/topics/x/y` 는 404 입니다. 서버는 이미 그 주소를 내보내고 있었고, 사용자는 네 링크가 전부 404 인 화면을 봤습니다. **순서가 있습니다 — 새 라우트는 프론트가 먼저입니다.**
|
||||
|
||||
고치기 전과 고친 뒤가 사용자에게는 같아 보였다. 앞에서는 눌러도 아무 일이 없었고, 뒤에서는 눌러서 404 를 만났다.
|
||||
|
||||
## 주제가 없는 기록
|
||||
|
||||
같은 시기에 주제 없이 게시된 기록이 이름 없는 주제 링크를 달고 있었다. 문서 머리말의 breadcrumb 과 탐색의 「주제 없음」 묶음 둘 다였다. 프로젝트 조각은 처음부터 조건부였는데 주제 쪽만 아니었다.
|
||||
같은 시기에 주제 없이 게시된 기록이 이름 없는 주제 링크를 달고 있었다. 문서 머리말의 breadcrumb 과 탐색의 「주제 없음」 묶음 둘 다였다.
|
||||
|
||||
프로젝트 조각은 처음부터 조건부였다. 주제 쪽만 조건이 없었고, 그 차이가 두 화면에서 같은 모양으로 나타났다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
|
||||
+14
-6
@@ -64,23 +64,31 @@ tech-log-frontend : 5cffe30 · 7093d84
|
||||
|
||||
## 간헐적으로 보인 이유
|
||||
|
||||
slug 생성이 `[a-z0-9]` 만 남기고 나머지를 버렸다. 한글 이름은 통째로 사라지므로 이름에 영문이 얼마나 섞였는지에 따라 결과가 갈린다.
|
||||
slug 생성이 영문 소문자와 숫자만 남기고 나머지를 버렸다. 한글 이름은 통째로 사라지므로, 결과가 이름에 영문이 얼마나 섞였는지에 따라 갈린다.
|
||||
|
||||
영문이 하나도 없으면 빈 문자열이 되어 폼이 요청 전에 거절한다. 영문이 앞에 붙어 있으면 그 부분만 남으므로 뒤가 다른 두 이름이 같은 slug 가 된다.
|
||||
| 이름 | 옛 규칙이 만든 slug | 무엇이 일어났나 |
|
||||
|---|---|---|
|
||||
| `인증` | (빈 문자열) | 폼이 요청 전에 거절 |
|
||||
| `Redis 캐시` | `redis` | 만들어짐 |
|
||||
| `Redis 클러스터` | `redis` | 두 번째가 충돌 |
|
||||
|
||||
`Redis 캐시` 와 `Redis 클러스터` 가 둘 다 `redis` 였다. 두 번째를 만들 때 충돌이 났고, 사용자에게는 「가끔 안 된다」로 보였다.
|
||||
사용자는 둘 다 만났다. 어느 쪽도 「한글이 버려졌다」로 보이지 않고, 하나는 폼 오류로 하나는 중복 오류로 나타난다.
|
||||
|
||||
> 규칙은 간헐적이었던 적이 없다. **보이지 않았을 뿐이다** — slug 생성이 `[a-z0-9]` 만 남기고 나머지를 버려서, 한글 이름은 아무것도 기여하지 못했다.
|
||||
|
||||
## 산술 분해로 로마자를 만든다
|
||||
|
||||
한글 음절은 초성·중성·종성이 정해진 순서로 조합된 코드다. 음절 코드에서 세 값을 산술로 분해할 수 있으므로 변환표가 필요 없고 결과가 결정적이다.
|
||||
한글 음절은 초성·중성·종성이 정해진 순서로 조합된 코드다. 음절 코드에서 세 값을 산술로 분해할 수 있으므로 변환표가 필요 없고, 같은 입력에 늘 같은 결과가 나온다.
|
||||
|
||||
`백엔드 아키텍처` → `baekendeu-akitekcheo`
|
||||
```text
|
||||
백엔드 아키텍처 → baekendeu-akitekcheo
|
||||
```
|
||||
|
||||
## 음운 변화 규칙을 뺀 이유
|
||||
|
||||
국어의 로마자 표기법에는 자모 대응 외에 음운 변화 규칙이 있다. 그것을 넣지 않았다.
|
||||
|
||||
slug 는 읽는 것이지 발음하는 것이 아니다. 음운 변화를 적용하면 같은 이름이 앞뒤 글자에 따라 다른 slug 가 되고, 그러면 같은 이름을 두 번 만들 때 결과가 갈린다.
|
||||
slug 는 읽는 것이지 발음하는 것이 아니다. 음운 변화를 적용하면 같은 글자가 앞뒤에 무엇이 오느냐에 따라 다르게 옮겨지고, 그러면 이름의 일부만 바뀌어도 앞쪽 slug 가 달라진다. 결정적이지 않은 slug 는 주소로 쓸 수 없다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
|
||||
+21
-7
@@ -14,6 +14,7 @@ evidence:
|
||||
- ../../../final/evidence/raw/db/decision-path-after-v15.txt
|
||||
- ../../../final/evidence/raw/api/decision-anchor-fixed.txt
|
||||
- ../../../final/evidence/raw/audit/dead-link-sweep.txt
|
||||
- ../../../final/evidence/raw/audit/link-audit.py
|
||||
sourceRevision: tech-log@2026-09-02
|
||||
source:
|
||||
- final/document.md#§9.2
|
||||
@@ -78,23 +79,34 @@ tech-log-frontend : fe6b56a
|
||||
:::evidence key="decision-path-404" alt="계약·게시 시점 경로 생성·저장 테이블·조회 시점 경로 생성·방문자·공개 라우트 여섯 참가자 사이의 순서도" caption=" " zoom="true"
|
||||
:::
|
||||
|
||||
계약은 결정의 공개 주소가 앵커라고 규정한다. 게시 시점의 `PublicPaths.forKind` 는 그 대신 경로를 만들어 `public_resource_projection` 에 저장한다. 조회 시점의 `PublicSql.pathOf` 가 저장된 주소를 읽고 방문자에게 링크로 내보낸다. 방문자가 그 주소를 요청하면 공개 라우트에는 목록 하나뿐이라 맞는 라우트가 없다.
|
||||
계약은 결정의 공개 주소가 목록 위의 앵커라고 규정한다. 게시 시점의 경로 생성기는 그 대신 목록 아래에 slug 를 붙인 경로를 만들어 공개 투영에 저장한다. 조회 시점의 다른 생성기가 저장된 주소를 읽어 방문자에게 링크로 내보낸다. 방문자가 그 주소를 요청하면 공개 라우트에는 목록 하나뿐이라 맞는 라우트가 없다.
|
||||
|
||||
## 계약은 이미 맞게 적혀 있었다
|
||||
계약은 고칠 것이 없었다. 공개 주소가 앵커라는 것이 이미 계약에 있었고, 만드는 쪽 두 곳이 그것을 따르지 않았다.
|
||||
|
||||
공개 주소가 `#{slug}` 앵커라는 것이 계약에 이미 있었고, 만드는 쪽 두 곳이 그것을 따르지 않았다.
|
||||
## 화면 코드에는 흔적이 없다
|
||||
|
||||
이 부류는 저장소를 훑어서는 나오지 않는다. 주소는 게시할 때 서버가 만들어 데이터베이스에 저장한 문자열이고, 화면은 그것을 받아 그대로 링크로 그린다. 코드에 `to=` 나 `href=` 리터럴로 남아 있지 않다.
|
||||
|
||||
감사 스크립트가 그 이유를 적어 두었다.
|
||||
|
||||
> 주소는 게시 시점에 서버가 만들어 DB(`public_resource_projection.navigation_path`)에 저장한 문자열이다. 그래서 저장소 안의 `to=` / `href=` 리터럴만 훑는 감사로는 잡히지 않는다 — 실제로 결정 링크가 그렇게 숨어 있었다.
|
||||
|
||||
## 저장된 행까지 고쳐야 한다
|
||||
|
||||
주소가 게시 시점에 굳어져 저장되므로 코드만 고치면 이미 게시된 링크는 깨진 채 남는다. V15 마이그레이션에서 저장된 행도 고쳤다.
|
||||
주소가 게시 시점에 굳어지므로 코드만 고치면 이미 게시된 링크는 깨진 채 남는다. 마이그레이션에서 저장된 행을 함께 고쳤다.
|
||||
|
||||
`public_route.slug` 도 손봤다. 마지막 슬래시 뒤를 자르면 앵커가 붙은 주소에서 `decisions#slug` 전체가 slug 로 저장된다. 앵커가 있으면 그 뒤를 조각으로 읽게 했다.
|
||||
공개 라우트의 slug 를 읽는 규칙도 함께 손봤다. 마지막 슬래시 뒤를 자르는 방식이면 앵커가 붙은 주소에서 `decisions#slug` 전체가 slug 로 저장된다. 앵커가 있으면 그 뒤를 조각으로 읽게 했다.
|
||||
|
||||
목록 항목이 앵커를 달 수 있도록 계약에 slug 를 더하고, 화면이 그 slug 를 element id 로 달고 앵커로 들어오면 데이터를 받아 그린 뒤 스크롤하게 했다.
|
||||
|
||||
## 두 겹 가드
|
||||
|
||||
`PublicPathsTest` 는 백엔드에서 종류마다 만들어 낸 경로가 실제 공개 라우트 패턴에 맞는지 본다.
|
||||
| 어디 | 무엇을 보나 | 이 부류가 또 나면 |
|
||||
|---|---|---|
|
||||
| 백엔드 `PublicPathsTest` | 종류마다 만들어 낸 경로가 실제 공개 라우트 패턴에 맞는가 | 만드는 쪽에서 멈춘다 |
|
||||
| 프론트 `resolvesToPublicRoute` | 서버가 준 주소가 라우트 표에 있는가 | 링크로 그리지 않아 방문자가 404 를 만나지 않는다 |
|
||||
|
||||
`resolvesToPublicRoute` 는 프론트에서 라우트 계약이 준 표에 서버가 준 주소를 맞춰 보고, 맞는 라우트가 없으면 링크로 그리지 않는다. 이 부류가 또 생겨도 방문자가 404 를 만나지는 않는다. 틀린 주소가 만들어지는 것 자체는 백엔드 쪽 검사가 잡는다.
|
||||
만드는 쪽에만 두면 이미 저장된 주소를 못 잡고, 그리는 쪽에만 두면 링크가 안 그려지는 것으로 끝나 원인이 남는다.
|
||||
|
||||
## 배포 뒤 전수 감사
|
||||
|
||||
@@ -103,6 +115,8 @@ tech-log-frontend : fe6b56a
|
||||
:::evidence key="dead-link-sweep" alt="서버가 내보내는 주소 35개를 전수로 훑은 감사 출력" caption=" " zoom="false"
|
||||
:::
|
||||
|
||||
같은 방식으로 다시 검사하는 스크립트를 증거와 함께 남겼다. 다음에 라우트를 더하면 그 스크립트를 다시 돌린다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
이 감사는 서버가 내보내는 주소만 본다. 본문 안에 작성자가 손으로 쓴 링크는 대상이 아니다.
|
||||
|
||||
+14
-4
@@ -76,23 +76,33 @@ tech-log-frontend : 344dadb · 805d400 · 8c5dbe1
|
||||
}
|
||||
```
|
||||
|
||||
`a` 는 구역 안의 모든 링크를 잡는다. 제목 안에 링크가 있으면 그 링크도 격자가 되고 첫 칸의 200px 에 갇힌다. 행을 위한 padding 까지 물려 h2 높이가 199px 이 됐다.
|
||||
선택자가 구역 클래스 아래의 `a` 이므로 그 구역 안의 모든 링크가 걸린다. 제목 안에 링크가 있으면 그 링크도 격자가 되고 첫 칸의 200px 에 갇힌다.
|
||||
|
||||
제목은 한 줄로 들어갈 길이였는데 200px 안에서 두 줄로 접혔고, 행을 위한 위아래 padding 까지 물려 h2 높이가 199px 이 됐다. 같은 이유로 주제 화면의 안내 문단도 행의 크기와 색으로 덮여 있었다.
|
||||
|
||||
사용자가 원인을 정확히 짚어 주었다 — 「디자인이 안 된 게 아니라 CSS 선택자가 새고 있습니다」.
|
||||
|
||||
## 아홉 구역에 같은 모양이 있었다
|
||||
|
||||
같은 형태가 다른 구역 아홉 곳에도 있었다. 선택자 51개를 `li > a` 처럼 그 배치를 쓰는 요소까지 좁혔다.
|
||||
|
||||
`li > a` 는 목록 항목의 바로 아래 링크만 잡는다. 제목 안의 링크는 목록 항목의 자식이 아니므로 걸리지 않는다.
|
||||
|
||||
## CSS 만으로 막지 않았다
|
||||
|
||||
선택자를 좁혀도 같은 구조가 다시 생기면 다시 샌다. 제목 안에 링크를 두지 않도록 구조를 바꿨고, 주제로 가는 길은 아래 한 줄이 맡는다.
|
||||
선택자를 좁히는 것은 같은 구조가 다시 생기면 다시 샌다. 제목 안에 링크를 두지 않도록 구조를 바꿨고, 주제로 가는 길은 아래 한 줄이 맡는다.
|
||||
|
||||
## 전역 규칙이 닿지 않는 화면
|
||||
|
||||
버튼에서 상자를 걷어내는 변경이 앱 전역 규칙만 고쳤다. 게시 기록·게시 흐름·워크플로 게이트는 CSS module 을 쓰므로 그 규칙이 닿지 않아, 다른 화면에서 상자를 걷어낸 뒤에도 「Snapshot 보기」·「게시 취소」·「적용」만 테두리와 파란 채움으로 남았다. 한 화면 안에서 두 언어가 섞여 더 눈에 띄었다.
|
||||
버튼에서 상자를 걷어내는 변경이 앱 전역 규칙만 고쳤다. 게시 기록·게시 흐름·워크플로 게이트는 CSS module 을 쓰므로 그 규칙이 닿지 않는다 — module 은 클래스 이름을 빌드 때 바꿔 전역 선택자와 겹치지 않게 만드는 방식이라, 전역에서 그 클래스를 가리킬 방법이 없다.
|
||||
|
||||
다른 화면에서 상자를 걷어낸 뒤에도 「Snapshot 보기」·「게시 취소」·「적용」 세 버튼만 테두리와 파란 채움으로 남았다. 한 화면 안에서 두 언어가 섞여 더 눈에 띄었다.
|
||||
|
||||
## 검사
|
||||
|
||||
`section-selector-scope.test.ts` 가 `.클래스 태그` 모양에 배치 속성(`display: grid|flex`, `grid-template-columns`, `padding`)이 걸려 있으면 멈춘다. 이미 좁혀 둔 곳은 `SETTLED` 로 명시한다. 색이나 글꼴만 거는 규칙은 새어도 티가 나지 않으므로 대상이 아니다.
|
||||
`section-selector-scope.test.ts` 가 `.클래스 태그` 모양에 배치 속성(`display: grid|flex`, `grid-template-columns`, `padding`)이 걸려 있으면 멈춘다. 이미 좁혀 둔 곳은 `SETTLED` 로 명시한다.
|
||||
|
||||
색이나 글꼴만 거는 규칙은 대상이 아니다. 새어도 티가 나지 않으므로 검사에 넣으면 결과가 늘 빨갛고, 그러면 곧 읽히지 않는다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
|
||||
+12
-3
@@ -59,17 +59,26 @@ tech-log-frontend : 68538f2
|
||||
|
||||
> 규칙이 없었던 게 아니라 **절반만 있었다.** 정본은 `.section-heading-row h2` 인데 그 안에 들어가지 않는 두 구역이 **크기만 각자 적어 두어 굵기를 아무도 정하지 않았고**, 그래서 기본값 400 으로 떨어졌다.
|
||||
|
||||
크기를 각자 적어 두었기 때문에 「규칙이 없다」로 보이지 않는다.
|
||||
크기를 각자 적어 두었기 때문에 「규칙이 없다」로 보이지 않는다. 선언이 있고 그 선언이 절반만 덮는다.
|
||||
|
||||
| | 정본 | 문제의 두 구역 |
|
||||
|---|---|---|
|
||||
| 크기 | 30px | 26px (각자 선언) |
|
||||
| 굵기 | 650 | 400 (아무도 선언하지 않음) |
|
||||
|
||||
같은 화면에 나란히 서 있는 구역들이라 이 둘만 급이 낮아 보였다.
|
||||
|
||||
## 처음에 잘못 판단한 것
|
||||
|
||||
> **이때 제가 저지른 판단 오류:** 처음에 grid/columns 만 측정하고 "정상"이라고 답했습니다. 사용자가 다시 지적한 뒤 **전체 페이지 스크린샷**을 찍어서야 26px/400 을 봤습니다. **프록시 지표가 아니라 보이는 것을 측정해야 합니다.**
|
||||
|
||||
격자와 열은 정상이었다. 어긋난 것은 글자 크기와 굵기였고, 격자를 재서는 그 값이 나오지 않는다.
|
||||
격자와 열은 정상이었다. 보고된 증상은 글자 크기와 굵기였는데 다른 축을 재고 답했다.
|
||||
|
||||
## 검사
|
||||
|
||||
`section-heading-rank.test.ts` 가 나란히 서는 구역 제목들이 정본과 같은 `font-size` 와 `font-weight` 를 쓰는지 CSS 를 파싱해 확인한다. 굵기를 빼 보고 실제로 멈추는 것을 확인했다.
|
||||
`section-heading-rank.test.ts` 가 나란히 서는 구역 제목들이 정본과 같은 `font-size` 와 `font-weight` 를 쓰는지 CSS 를 파싱해 확인한다.
|
||||
|
||||
굵기를 빼 보고 실제로 멈추는 것을 확인한 뒤 커밋했다. 확인하지 않았다면 그 구역들이 원래 정본을 쓰고 있어서 통과한 것인지 검사가 그 구역을 안 보는 것인지 구별되지 않는다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
|
||||
+11
-13
@@ -36,19 +36,21 @@ source:
|
||||
|
||||
### 1. 보고된 증상과 같은 축의 값을 잰다
|
||||
|
||||
「제목이 작아 보인다」는 글자 크기와 굵기다. 격자와 열은 다른 축이다.
|
||||
「제목이 작아 보인다」는 글자 크기와 굵기다. 격자와 열은 다른 축이라, 그것이 정상이라는 답으로는 보고한 사람이 본 것을 설명하지 못한다.
|
||||
|
||||
목록 간격을 바운딩 박스로만 재서 엉뚱한 구역을 결함으로 지목한 적도 있다. 박스가 겹치는 것과 눈에 보이는 간격이 다르기 때문이다.
|
||||
|
||||
### 2. 촬영을 스크립트로 고정한다
|
||||
|
||||
손으로 찍으면 뷰포트와 축소 배율이 매번 달라진다.
|
||||
손으로 찍으면 뷰포트와 축소 배율이 매번 달라진다. 브라우저 세션이 리셋되면 창은 약 877px 로 돌아가는데 이 사이트의 분기는 1179·1050·900·767 이라, 방문자 대부분이 보지 않는 배치를 놓고 디자인을 논하게 된다.
|
||||
|
||||
### 3. 폭을 고정해 여러 개를 돌고, 폭마다 측정값도 남긴다
|
||||
### 3. 폭을 고정해 여러 개를 돌고 폭마다 측정도 함께 남긴다
|
||||
|
||||
스크린샷만 남기면 나중에 그 값이 얼마였는지 다시 잴 수 없다.
|
||||
스크린샷만 남기면 나중에 그 값이 얼마였는지 다시 잴 수 없다. 폭마다 계산된 스타일을 함께 적어 두면 다음에 무엇이 달라졌는지 대조할 수 있다.
|
||||
|
||||
### 4. 전체 페이지를 한 장으로 찍지 않는다
|
||||
|
||||
축소되어 글자 크기가 실제와 달라진다. 화면 높이만큼 잘라 찍는다.
|
||||
축소되어 글자 크기가 실제와 달라진다. 1425x4466 이 638x2000 으로 들어오면 17px 글자가 7~8px 이 되고, 그 그림에서는 크기 문제를 볼 수 없다. 화면 높이만큼 잘라 찍는다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
@@ -60,15 +62,11 @@ source:
|
||||
## 예외
|
||||
|
||||
- 측정 대상이 좌표나 간격 자체라면 바운딩 박스는 프록시가 아니라 대상이다.
|
||||
|
||||
- CSS 선언이 정본과 같은지를 보는 검사는 렌더 결과를 재지 않는다. 그 검사가 덮는 범위를 알고 쓰면 된다.
|
||||
|
||||
## 예시
|
||||
|
||||
- 격자와 열만 재고 「정상」이라 답했다. 사용자가 다시 지적한 뒤 전체 페이지 스크린샷을 찍어서야 26px 과 400 을 봤다.
|
||||
|
||||
- 목록 간격을 바운딩 박스로만 재서 「판단 기준」을 결함으로 잘못 지목한 적이 있다.
|
||||
|
||||
- 브라우저 세션이 리셋되면 창이 약 877px 로 돌아가는데 이 사이트의 분기는 1179·1050·900·767 이라, 877px 에서 본 배치는 방문자 대부분이 보는 배치가 아니다.
|
||||
|
||||
- 전체 페이지를 한 장으로 찍으면 1425x4466 이 638x2000 으로 들어와 17px 글자가 7~8px 이 된다.
|
||||
- 격자와 열만 재고 「정상」이라 답했다. 사용자가 다시 지적한 뒤 전체 페이지 스크린샷을 찍어서야 26px 과 400 을 봤다
|
||||
- 목록 간격을 바운딩 박스로만 재서 엉뚱한 구역을 결함으로 지목한 적이 있다
|
||||
- 브라우저 세션이 리셋되면 창이 약 877px 로 돌아가는데 이 사이트의 분기는 1179·1050·900·767 이다
|
||||
- 전체 페이지를 한 장으로 찍으면 1425x4466 이 638x2000 으로 들어와 17px 글자가 7~8px 이 된다
|
||||
|
||||
+13
-12
@@ -27,25 +27,29 @@ source:
|
||||
|
||||
## 목적
|
||||
|
||||
「디자인이 안 된 것처럼 보인다」는 보고의 원인이 되는 선택자 누출을 막는다. 이 부류는 규칙 자체는 맞고 걸리는 대상이 넓다.
|
||||
「디자인이 안 된 것처럼 보인다」는 보고의 원인이 되는 선택자 누출을 막는다.
|
||||
|
||||
이 부류는 규칙 자체는 맞고 걸리는 대상이 넓다. 그래서 규칙을 읽어서는 틀린 것을 찾을 수 없고, 그려진 결과를 봐야 한다.
|
||||
|
||||
## 규칙
|
||||
|
||||
### 1. 배치 속성은 그 배치를 쓰는 요소까지 좁혀 적는다
|
||||
|
||||
`display: grid|flex`, `grid-template-columns`, `padding` 을 구역 클래스 아래 태그 선택자로 걸지 않는다.
|
||||
`display: grid|flex`·`grid-template-columns`·`padding` 을 구역 클래스 아래 태그 선택자로 걸지 않는다.
|
||||
|
||||
행을 위한 격자를 구역 전체에 걸면 제목 안의 링크도 그 격자가 된다. 첫 칸 너비에 갇혀 접히고 행용 padding 까지 물린다.
|
||||
|
||||
### 2. CSS 만으로 막지 않고 구조도 본다
|
||||
|
||||
선택자를 좁히면 같은 구조가 다시 생겼을 때 다시 샌다. 제목 안에 링크를 두지 않는 편이 낫다.
|
||||
선택자를 좁히면 같은 구조가 다시 생겼을 때 다시 샌다. 제목 안에 링크를 두지 않는 편이 낫고, 주제로 가는 길은 별도의 한 줄이 맡는다.
|
||||
|
||||
### 3. 이미 좁혀 둔 곳은 검사에서 명시적으로 뺀다
|
||||
|
||||
예외를 적어 두지 않으면 검사 결과가 늘 빨갛고 곧 읽히지 않는다.
|
||||
예외를 적어 두지 않으면 검사 결과가 늘 빨갛고 곧 읽히지 않는다. 뺀 것이 코드에 이름으로 남아 다음 사람이 세어 볼 수 있어야 한다.
|
||||
|
||||
### 4. 전역 규칙과 CSS module 이 섞인 화면을 따로 센다
|
||||
|
||||
전역 규칙을 고쳐도 module 을 쓰는 화면에는 닿지 않는다. 한 화면 안에서 두 언어가 섞이면 더 눈에 띈다.
|
||||
module 은 클래스 이름을 빌드 때 바꿔 전역 선택자와 겹치지 않게 만든다. 그래서 전역 규칙을 고쳐도 module 을 쓰는 화면에는 닿지 않고, 한 화면 안에서 두 언어가 섞이면 더 눈에 띈다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
@@ -56,14 +60,11 @@ source:
|
||||
|
||||
## 예외
|
||||
|
||||
- 색이나 글꼴만 거는 규칙은 새어도 티가 나지 않으므로 대상이 아니다.
|
||||
|
||||
- 색이나 글꼴만 거는 규칙은 새어도 티가 나지 않으므로 대상이 아니다. 검사에 넣으면 결과가 늘 빨갛다.
|
||||
- 구역 안의 모든 같은 태그가 실제로 같은 배치를 써야 하는 화면이라면 좁히지 않아도 된다. 그때는 그 의도를 검사의 예외 목록에 적는다.
|
||||
|
||||
## 예시
|
||||
|
||||
- 비교 행을 위한 격자가 제목 안의 링크까지 잡아 제목이 200px 칸에 갇혔다.
|
||||
|
||||
- 같은 모양이 다른 구역 아홉 곳에도 있어 선택자 51개를 고쳤다.
|
||||
|
||||
`section-selector-scope.test.ts` 가 `.클래스 태그` 모양에 배치 속성이 걸려 있으면 멈춘다.
|
||||
- 비교 행을 위한 격자가 제목 안의 링크까지 잡아 제목이 200px 칸에 갇히고 h2 높이가 199px 이 됐다
|
||||
- 같은 모양이 다른 구역 아홉 곳에도 있어 선택자 51개를 고쳤다
|
||||
- 전역 규칙만 고쳐서 CSS module 을 쓰는 세 버튼만 테두리와 파란 채움으로 남았다
|
||||
|
||||
+10
-4
@@ -61,23 +61,29 @@ tech-log-backend : 911e8ba — 없던 두 컨트롤러를 구현
|
||||
|
||||
편집기는 질문 목록을 받아 고를 수 있게 그린다. 목록이 비면 「이 프로젝트에 열린 질문이 없습니다」를 적는다.
|
||||
|
||||
요청이 실패했을 때도 빈 배열이 되고 있었다.
|
||||
요청이 실패했을 때도 빈 배열이 되고 있었다. 그래서 「없다」와 「못 읽었다」가 같은 화면이 됐다.
|
||||
|
||||
서버는 404 를 주고 있었다. 그 두 목록 조회에 컨트롤러가 없었고, 계약에는 선언돼 있어 프론트가 그것을 믿고 불렀다.
|
||||
|
||||
## 작성자가 무엇으로 읽었나
|
||||
|
||||
작성자는 자기가 쓴 것과 화면을 대조한다. 화면이 「없습니다」라고 하면 아직 안 썼거나 게시하지 않았다고 읽는다.
|
||||
|
||||
이 경우에는 넷이 있었고 공개 사이트에도 나오고 있었다. 편집기만 못 읽고 있었다.
|
||||
이 경우에는 넷이 있었고 공개 사이트에도 나오고 있었다. 두 화면이 같은 데이터베이스를 보는데 한쪽만 비어 있었으므로, 공개 사이트를 함께 보지 않으면 알아챌 방법이 없었다.
|
||||
|
||||
작성 도구에서 이 부류가 특히 오래 숨는다. 읽기 전용 화면이면 방문자가 「데이터가 없나 보다」로 넘어가지만, 작성 도구에서는 그 오독이 곧바로 작업 판단이 된다 — 없다고 읽으면 다시 쓰게 된다.
|
||||
|
||||
## 못 읽었다고 적는다
|
||||
|
||||
> 거짓말을 하느니 못 읽었다고 말한다.
|
||||
|
||||
요청이 실패하면 실패했다고 적는다. 0건은 0건이라고 적는다.
|
||||
요청이 실패하면 실패했다고 적고 0건은 0건이라고 적는다. 이 둘을 구분할 수 있어야 작성자가 다음에 무엇을 할지 정한다 — 실패면 다시 부르거나 서버를 보고, 0건이면 쓰면 된다.
|
||||
|
||||
## 같은 판단을 다른 화면에
|
||||
|
||||
주제 탭에도 같은 판단을 적용했다. 탭 하나를 못 받아도 탭 줄과 나머지 탭은 그대로 그려지고, 못 받은 탭에는 못 받았다고 적는다.
|
||||
주제 탭에도 같은 판단을 적용했다. 탭 하나를 못 받아도 탭 줄과 나머지 탭은 그대로 그리고, 못 받은 탭에는 못 받았다고 적는다.
|
||||
|
||||
탭 줄이 남는 것이 중요하다. 탭 줄까지 사라지면 그 주제가 없는 것처럼 보이고, 그러면 못 읽은 범위가 화면에서 더 넓어진다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
|
||||
+13
-5
@@ -58,19 +58,27 @@ tech-log-frontend : 6e784ed · fd73bc8 · 3bb724b
|
||||
|
||||
## 묶어 읽으면 한쪽이 전체를 끌고 내려간다
|
||||
|
||||
편집기가 질문 목록과 결정 목록을 하나로 묶어 기다리고 있었다. 결정 쪽 연산에 컨트롤러가 없어 404 가 났고, 화면은 두 목록을 다 못 받은 것으로 그렸다.
|
||||
홈 편집기가 질문 목록과 결정 목록을 하나로 묶어 기다리고 있었다. 결정 쪽 연산에 컨트롤러가 없어 404 가 났고, 화면은 두 목록을 다 못 받은 것으로 그렸다.
|
||||
|
||||
질문 목록은 정상적으로 오고 있었다. 둘을 따로 읽도록 갈랐다.
|
||||
질문 목록은 정상적으로 오고 있었다. 묶어 기다리면 하나라도 거절되는 순간 전체가 거절되므로, 성공한 응답이 있어도 쓸 수 없다.
|
||||
|
||||
## 동기적으로 던지면 거절 처리를 지나지 않는다
|
||||
둘을 따로 읽도록 갈랐다. 실패한 목록에만 못 받았다고 적고 나머지는 그대로 그린다.
|
||||
|
||||
## 거절과 던짐이 다른 경로를 탄다
|
||||
|
||||
같은 코드에서 더 미묘한 변종이 났다.
|
||||
|
||||
> `Promise.all([gateway.foo()])` 은 foo 가 **거절하는 것만** 잡는다. 호출이 **동기적으로 던지면** 배열을 만드는 중에 터져 rejection handler 를 지나지 못하고, 그러면 홈 focus 한 칸 때문에 대시보드 전체가 빈 화면이 된다.
|
||||
|
||||
배열을 만드는 표현식 안에서 던지면 그 표현식이 완성되지 않으므로 거절 처리기가 붙을 대상이 없다.
|
||||
배열 리터럴 안에서 함수를 부르는 시점은 `Promise.all` 에 배열이 넘어가기 전이다. 그 호출이 동기적으로 던지면 배열이 완성되지 않으므로 `Promise.all` 이 시작조차 하지 않고, 거기에 붙인 거절 처리기도 붙을 대상이 없다.
|
||||
|
||||
거절만 잡는 처리로는 부족하다. 던지는 경로도 함께 잡아야 한 칸의 실패가 화면 전체로 번지지 않는다.
|
||||
|
||||
## 탭에도 같은 판단을
|
||||
|
||||
탭 줄은 목록 하나로 그리고 상세는 고른 탭만 받는다. 상세 하나를 못 받아도 탭 줄과 나머지 탭은 그대로 그리고, 못 받은 탭에는 못 받았다고 적는다.
|
||||
탭 줄은 목록 호출 하나가 주는 전부이고 상세는 고른 탭만 그때 받는다. 상세 하나를 못 받아도 탭 줄과 나머지 탭은 그대로 그리고, 못 받은 탭에는 못 받았다고 적는다.
|
||||
|
||||
이 구조에서는 실패가 번질 수 있는 범위가 처음부터 한 탭으로 좁다. 상세를 미리 다 받아 두는 구조였다면 하나가 실패할 때 어디까지 그릴지를 다시 정해야 했다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
|
||||
+12
-2
@@ -62,20 +62,30 @@ tech-log-design-package : 76a7ccb
|
||||
|
||||
## null 을 돌려주고 걸러 내면 흔적이 없다
|
||||
|
||||
매퍼가 아는 종류가 아니면 `null` 을 돌려준다. 호출부는 그 목록에서 `null` 을 걸러 낸다.
|
||||
매퍼가 아는 종류가 아니면 `null` 을 돌려주고, 호출부는 그 목록에서 `null` 을 걸러 낸다.
|
||||
|
||||
이 조합에서는 오류가 나지 않고 빈 줄도 생기지 않는다. 목록의 길이만 줄어든다.
|
||||
|
||||
| 무엇이 남나 | 있음 |
|
||||
|---|---|
|
||||
| 오류 로그 | x |
|
||||
| 화면의 빈 줄 | x |
|
||||
| 목록 길이의 변화 | o |
|
||||
|
||||
목록에 몇 개가 있어야 하는지 아는 사람만 알아챌 수 있고, 공개 화면에서는 그것을 아는 사람이 작성자뿐이다.
|
||||
|
||||
## 응답 모양이 다른 목록에 다른 매퍼를 썼다
|
||||
|
||||
프로젝트 기록 목록과 탐색의 지식 목록은 응답 모양이 다르다. 프로젝트 쪽은 관계 항목을 그대로 실어 요약도 주제도 게시일도 없었고, 지식 목록은 처음부터 그 칸들을 갖고 있었다.
|
||||
|
||||
모양이 다른데 매퍼를 공유하면서 종류 판정까지 그쪽 것을 따랐다.
|
||||
모양이 다른데 매퍼를 공유하면서 종류 판정까지 그쪽 것을 따랐다. 지식 목록의 매퍼는 CASE 와 REFERENCE 만 다루면 되는 화면을 위해 만들어졌으므로, 질문과 개념을 받으면 `null` 을 돌려준다.
|
||||
|
||||
## 고친 것
|
||||
|
||||
응답 모양에 맞는 매퍼를 쓰고 모든 종류를 싣게 했다. 목록 항목에 요약과 주제와 게시일을 더해 「제목만 있고 가운뎃점만 남은」 줄을 없앴다.
|
||||
|
||||
이 목록은 계약도 함께 고쳐야 했다. 관계 항목을 그대로 싣던 구조라 요약·주제·게시일을 담을 칸이 응답에 없었다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
`null` 을 돌려주고 호출부가 거르는 매퍼가 다른 목록에도 남아 있는지는 세지 않았다.
|
||||
|
||||
+12
-9
@@ -29,23 +29,27 @@ source:
|
||||
|
||||
매번 우는 검사가 읽히지 않게 되는 것을 막는다.
|
||||
|
||||
> 매번 늑대를 외치는 검사는 읽히지 않게 되고, 진짜 실패가 그 옆에 눈에 띄지 않은 채 앉아 있게 된다.
|
||||
|
||||
## 규칙
|
||||
|
||||
### 1. 기대된 실패는 조건을 적어 뺀다
|
||||
|
||||
미리보기가 아직 없는 문서는 현재 미리보기를 물으면 404 를 답하고, 화면은 그것을 「미리보기를 만드세요」로 바꾼다. 이런 응답은 실패가 아니다.
|
||||
미리보기가 아직 없는 문서는 현재 미리보기를 물으면 404 를 답하고, 화면은 그것을 「미리보기를 만드세요」로 바꾼다. 이 404 는 정상 동작이 오류 상태 코드로 나타난 것이지 실패가 아니다.
|
||||
|
||||
로그인 전 세션 탐침의 401 도 같은 부류다. 어느 쪽도 사람이 고칠 것이 없다.
|
||||
|
||||
### 2. 뺀 나머지는 전부 실패시킨다
|
||||
|
||||
조건에 걸리지 않는 4xx 와 5xx 는 모두 스윕을 실패시킨다.
|
||||
조건에 걸리지 않는 4xx 와 5xx 는 모두 스윕을 실패시킨다. 「이건 아마 괜찮을 것」이라는 판단을 스윕 안에 두지 않는다.
|
||||
|
||||
### 3. 조건을 적을 수 없으면 빼지 않는다
|
||||
|
||||
조건 없이 빼면 진짜 실패도 같이 빠진다.
|
||||
조건 없이 빼면 진짜 실패도 같이 빠진다. 어떤 요청이 왜 그 응답을 받는지 한 줄로 적을 수 없으면 그것은 아직 기대된 실패가 아니다.
|
||||
|
||||
### 4. 뺀 조건을 사람이 읽을 수 있는 곳에 남긴다
|
||||
|
||||
왜 그 응답이 기대된 것인지 적혀 있지 않으면 다음 사람이 조건을 넓힌다.
|
||||
왜 그 응답이 기대된 것인지 적혀 있지 않으면 다음 사람이 조건을 넓힌다. 비슷한 404 가 하나 더 나왔을 때 같은 조건에 넣을지 판단할 근거가 없기 때문이다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
@@ -56,11 +60,10 @@ source:
|
||||
## 예외
|
||||
|
||||
- 기계가 판정하고 사람이 결과를 읽지 않는 검사라면 빨간 줄이 쌓여도 무뎌지지 않는다. 그래도 통과 기준은 정해야 한다.
|
||||
- 그 응답이 정말 고쳐야 하는 것이면 조건으로 빼지 말고 고친다. 뺄지 고칠지는 사람이 고칠 것이 있는가로 가른다.
|
||||
|
||||
## 예시
|
||||
|
||||
- 미리보기가 없는 문서의 404 를 스윕이 실패로 셌다. 건강한 배포 아래에 매번 같은 빨간 줄이 남았다.
|
||||
|
||||
> 매번 늑대를 외치는 검사는 읽히지 않게 되고, 진짜 실패가 그 옆에 눈에 띄지 않은 채 앉아 있게 된다.
|
||||
|
||||
- 로그인 전 세션 탐침의 401 도 같은 부류라 같은 조건으로 제외하고, 나머지 4xx·5xx 는 전부 스윕을 실패시킨다.
|
||||
- 미리보기가 없는 문서의 404 를 스윕이 실패로 셌다. 건강한 배포 아래에 매번 같은 빨간 줄이 남았다
|
||||
- 로그인 전 세션 탐침의 401 도 같은 부류라 같은 조건으로 제외했다
|
||||
- 나머지 4xx·5xx 는 전부 스윕을 실패시킨다
|
||||
|
||||
+19
-11
@@ -28,7 +28,9 @@ source:
|
||||
|
||||
## 목적
|
||||
|
||||
작성자가 「아직 안 썼다」와 「못 읽었다」를 구분할 수 있게 한다. 이 둘이 같은 화면이면 작성자는 다음에 무엇을 할지 정할 수 없다.
|
||||
작성자가 「아직 안 썼다」와 「못 읽었다」를 구분할 수 있게 한다.
|
||||
|
||||
이 둘이 같은 화면이면 작성자는 다음에 무엇을 할지 정할 수 없다. 실패면 다시 부르거나 서버를 봐야 하고 0건이면 쓰면 되는데, 화면이 「없습니다」 하나로 답하면 뒤쪽으로 읽고 다시 쓰게 된다.
|
||||
|
||||
## 규칙
|
||||
|
||||
@@ -36,17 +38,25 @@ source:
|
||||
|
||||
빈 배열로 삼키지 않는다. 0건과 실패는 다른 문구를 쓴다.
|
||||
|
||||
목록 응답을 받아 그리는 코드에서 실패 경로가 빈 배열을 만들면, 그 아래의 「비어 있으면 이 문구」 분기가 두 경우를 같은 화면으로 만든다. 실패를 빈 값으로 접는 지점을 없애야 한다.
|
||||
|
||||
### 2. 한 칸의 실패가 옆 칸을 끌고 내려가지 않게 한다
|
||||
|
||||
여러 목록을 하나로 묶어 기다리면 한쪽의 실패가 전체를 실패로 만든다. 따로 읽고 실패한 목록에만 적는다.
|
||||
여러 목록을 하나로 묶어 기다리면 하나라도 거절되는 순간 전체가 거절된다. 성공한 응답이 있어도 쓸 수 없다.
|
||||
|
||||
따로 읽고 실패한 목록에만 적는다. 화면에 여러 묶음이 있으면 실패가 번질 수 있는 범위를 그 묶음 하나로 좁힌다.
|
||||
|
||||
### 3. 거절만 잡는 처리로는 부족하다
|
||||
|
||||
호출이 동기적으로 던지면 그 처리기를 지나지 않는다. 던지는 경로도 잡는다.
|
||||
호출이 동기적으로 던지면 그 처리기를 지나지 않는다. 배열 리터럴 안에서 부르는 함수가 던지면 배열이 완성되지 않으므로, 거기에 붙인 거절 처리기도 붙을 대상이 없다.
|
||||
|
||||
던지는 경로도 함께 잡아야 한 칸의 실패가 화면 전체로 번지지 않는다.
|
||||
|
||||
### 4. 항목을 걸러 낼 때 걸러 낸 것을 세어 둔다
|
||||
|
||||
매퍼가 모르는 종류에 빈 값을 돌려주고 호출부가 거르면, 목록이 한 줄 짧아지는 것 말고는 흔적이 없다.
|
||||
매퍼가 모르는 종류에 빈 값을 돌려주고 호출부가 그것을 거르면, 오류도 빈 줄도 남지 않고 목록 길이만 줄어든다.
|
||||
|
||||
목록에 몇 개가 있어야 하는지 아는 사람만 알아챌 수 있다. 공개 화면에서는 그것을 아는 사람이 작성자뿐이다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
@@ -58,13 +68,11 @@ source:
|
||||
## 예외
|
||||
|
||||
- 정말로 0건인 것과 못 읽은 것을 구분할 수 없는 화면이라면 그 구분을 먼저 만든다. 구분 없이 문구만 바꾸면 0건이 실패로 읽힌다.
|
||||
|
||||
- 읽는 사람이 그 데이터를 만들지 않는 화면 — 공개 조회 — 에서는 실패를 화면 전체의 오류로 다뤄도 된다.
|
||||
- 읽는 사람이 그 데이터를 만들지 않는 화면에서는 실패를 화면 전체의 오류로 다뤄도 된다. 방문자에게는 어느 목록이 실패했는지가 할 일을 바꾸지 않는다.
|
||||
|
||||
## 예시
|
||||
|
||||
- 「이 프로젝트에 열린 질문이 없습니다」가 적혀 있는 동안 그 프로젝트에는 질문이 넷 있었고 공개 사이트에도 나오고 있었다.
|
||||
|
||||
- 탭 하나를 못 받아도 탭 줄과 나머지 탭은 그대로 그리고, 못 받은 탭에는 못 받았다고 적는다.
|
||||
|
||||
- 매퍼가 모르는 종류에 빈 값을 돌려주고 호출부가 걸러 내, 질문과 개념이 목록에서 사라졌다.
|
||||
- 「이 프로젝트에 열린 질문이 없습니다」가 적혀 있는 동안 그 프로젝트에는 질문이 넷 있었고 공개 사이트에도 나오고 있었다
|
||||
- 결정 목록만 404 인데 함께 묶어 읽은 질문 목록까지 「불러오지 못했습니다」가 됐다
|
||||
- 탭 하나를 못 받아도 탭 줄과 나머지 탭은 그대로 그리고, 못 받은 탭에는 못 받았다고 적는다
|
||||
- 매퍼가 모르는 종류에 빈 값을 돌려주고 호출부가 걸러 내, 질문과 개념이 목록에서 사라졌다
|
||||
|
||||
+10
-2
@@ -61,7 +61,13 @@ Dockerfile 이 그 경고를 문자 그대로 적어 두고 있는데도 빠뜨
|
||||
|
||||
인자를 넘기지 않으면 기본값이 들어간다. 그 기본값은 존재하지 않는 주소다.
|
||||
|
||||
## 요구하는 인자 전부
|
||||
## 빌드도 배포도 성공한다
|
||||
|
||||
빌드는 인자가 없어도 성공한다. 이미지도 정상적으로 올라오고 파드도 healthy 다.
|
||||
|
||||
드러나는 곳은 배포본이 첫 요청을 낼 때다. 브라우저가 존재하지 않는 호스트로 요청을 보내고, 화면은 데이터를 받지 못한다.
|
||||
|
||||
Dockerfile 이 그 경고를 문자 그대로 적어 두고 있었는데도 빠뜨렸다.
|
||||
|
||||
```text
|
||||
APP_PROFILE=production
|
||||
@@ -76,8 +82,10 @@ SOURCE_DATE_EPOCH
|
||||
|
||||
`kubectl rollout undo` 로 이전 리비전으로 되돌린 뒤 인자를 넣어 다시 빌드하고 다시 올렸다.
|
||||
|
||||
되돌리는 것이 빨랐던 이유는 이전 이미지가 클러스터에 아직 있어서다. 레지스트리가 없어도 롤아웃 이력은 남으므로, 이미지를 다시 import 하지 않고 태그만 되돌리면 된다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
빌드가 이 인자를 요구하도록 막지 않았다. 빠뜨리면 여전히 빌드는 성공하고 배포본만 틀린다. 인자 목록을 적어 두고 메모리에 한 건 남긴 것으로 그쳤다 — `techlog-deploy-runtime-api-base.md`.
|
||||
빌드가 이 인자를 요구하도록 막지 않았다. 빠뜨리면 여전히 빌드는 성공하고 배포본만 틀린다. 지금 남은 것은 인자 목록을 적어 둔 것과 메모리 하나다.
|
||||
|
||||
<!-- body:end -->
|
||||
|
||||
+14
-3
@@ -62,17 +62,28 @@ tech-log-frontend : 83409be
|
||||
|
||||
헬스 판정은 헬스 엔드포인트가 응답하는지를 본다. nginx 프로세스가 살아 있고 그 경로를 돌려주면 통과한다.
|
||||
|
||||
SPA 가 부팅에 필요한 설정 파일은 그 판정에 들어 있지 않다.
|
||||
SPA 가 부팅에 필요한 설정 파일은 그 판정에 들어 있지 않다. 그래서 파드는 healthy 로 올라오고 사이트만 안 된다.
|
||||
|
||||
## 빌드가 쓴 권한
|
||||
|
||||
빌드가 그 파일을 0600 으로 쓴다. 파일을 만든 사용자만 읽을 수 있고, nginx 를 돌리는 사용자는 다른 사용자다.
|
||||
빌드가 그 설정 파일을 0600 으로 쓴다. 파일을 만든 사용자만 읽을 수 있고, nginx 를 돌리는 사용자는 다른 사용자다.
|
||||
|
||||
증상이 404 가 아니라 403 이라는 것이 원인을 좁혔다. 404 면 파일이 없는 것이고 403 이면 파일은 있는데 읽지 못하는 것이므로, 이미지에 파일이 들어갔는지부터 확인할 필요가 없었다.
|
||||
|
||||
이미지가 권한을 정규화하도록 고쳤다.
|
||||
|
||||
## 브라우저가 묻는 주소
|
||||
|
||||
같은 배포에서 favicon 도 404 였다. `index.html` 이 `public/favicon.svg` 를 참조한 적이 없다. 파일은 이미지에 들어 있었고 nginx 도 서빙했지만, 브라우저는 참조가 없으면 `/favicon.ico` 를 묻는다. 그 이름의 파일이 없어 404 를 받고 기본 아이콘으로 떨어졌다.
|
||||
같은 배포에서 favicon 도 404 였는데 원인이 달랐다. `index.html` 이 `public/favicon.svg` 를 참조한 적이 없다.
|
||||
|
||||
파일은 이미지에 들어 있었고 nginx 도 서빙했다. 브라우저는 참조가 없으면 `/favicon.ico` 를 묻고, 그 이름의 파일이 없어 404 를 받고 기본 아이콘으로 떨어졌다.
|
||||
|
||||
| | 파일이 이미지에 있나 | nginx 가 서빙하나 | 무엇이 문제였나 |
|
||||
|---|---|---|---|
|
||||
| 설정 파일 | o | o | 권한이 0600 이라 읽지 못함 |
|
||||
| favicon | o | o | 아무도 그 이름을 요청하지 않음 |
|
||||
|
||||
둘 다 「파일이 없다」가 아니었고, 그래서 이미지를 열어 파일을 찾는 방식으로는 둘 다 정상으로 보였다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
|
||||
+17
-7
@@ -37,15 +37,13 @@ source:
|
||||
→ kube-system 의 containerd import Job → kubectl set image
|
||||
```
|
||||
|
||||
공개 Hub 는 소스가 들어간 이미지라 쓸 수 없다. k3s 의 containerd 소켓은 root 전용이라 사용자 셸에서 닿지 않는다. 그래서 클러스터 안에 일회성 Job 을 띄워 tar 를 import 한다.
|
||||
|
||||
## 배포 단위가 하나다
|
||||
공개 Hub 는 소스가 들어간 이미지라 쓸 수 없다. k3s 의 containerd 소켓은 root 전용이라 사용자 셸에서 닿지 않는다. 그래서 클러스터 안에 일회성 Job 을 띄워 tar 를 import 한다 — Job 은 클러스터 권한으로 도므로 그 소켓에 닿는다.
|
||||
|
||||
배포 단위는 `hyeonworks.com` 하나이고 서브도메인을 쓰지 않는다. 공개는 `/`, API 는 `/api` 다.
|
||||
|
||||
## 무엇이 이미지 안에서 굳는가
|
||||
## 빌드 인자는 이미지에 굳는다
|
||||
|
||||
빌드 인자는 이미지에 굳는다. 런타임 환경 변수가 아니므로 배포한 뒤에 바꿀 수 없고, 잘못 넣으면 다시 빌드해서 다시 올려야 한다.
|
||||
이 경로에서 설정은 두 가지 방식으로 들어간다. 런타임 환경 변수는 배포한 뒤에도 바꿀 수 있고, 빌드 인자는 이미지에 굳어 다시 빌드해야 바뀐다.
|
||||
|
||||
프론트 이미지가 요구하는 인자는 이만큼이다.
|
||||
|
||||
@@ -58,14 +56,26 @@ CI_RUNNER_IMAGE=node@sha256:… ← 반드시 @sha256 다이
|
||||
SOURCE_DATE_EPOCH
|
||||
```
|
||||
|
||||
넘기지 않으면 기본값이 굳는다. 빌드는 성공하고 이미지도 정상적으로 올라오므로, 잘못된 값은 배포본이 요청을 내보낼 때 처음 드러난다.
|
||||
|
||||
백엔드 이미지는 Dockerfile 이 `src/` 아래에 있고 `RELEASE_VERSION`·`BUILD_VERSION`·`GIT_SHA`·`SOURCE_URL` 을 받는다. 태그는 짧은 SHA 일곱 자이고 배포된 것과 맞춰야 한다.
|
||||
|
||||
## 빌드 산출물에 커밋 해시가 들어간다
|
||||
|
||||
백엔드 빌드 산출물 이름에 커밋 해시가 들어간다. 작업 트리가 더러우면 해시가 달라져 stale 산출물 검사가 멈춘다. 커밋한 뒤에 빌드를 돌려야 한다.
|
||||
백엔드 빌드 산출물 이름에 커밋 해시가 들어간다. 작업 트리가 더러우면 해시가 달라져 stale 산출물 검사가 멈추므로, 커밋한 뒤에 빌드를 돌려야 한다.
|
||||
|
||||
이 순서는 산출물이 어느 커밋에서 나왔는지를 이름으로 추적하려고 둔 것이다. 그래서 순서를 지키지 않으면 검사가 통과하지 않고, 검사를 끄면 추적이 사라진다.
|
||||
|
||||
## 이 경로가 늦게 알려 주는 것
|
||||
|
||||
컨테이너 안의 파일 권한, nginx 가 서빙하기로 한 파일 목록, 빌드에 굳은 주소는 전부 이 단계 뒤에 드러난다.
|
||||
이미지가 healthy 로 올라오는 것과 사이트가 동작하는 것은 다르다.
|
||||
|
||||
| 무엇이 | 언제 드러나나 |
|
||||
|---|---|
|
||||
| 컨테이너 안의 파일 권한 | 그 파일을 처음 요청할 때 |
|
||||
| nginx 가 서빙하기로 한 파일 목록 | 목록에 없는 파일을 요청할 때 |
|
||||
| 빌드에 굳은 주소 | 배포본이 그 주소로 요청을 낼 때 |
|
||||
|
||||
셋 다 헬스 판정을 지난다. 헬스 엔드포인트는 그 파일도 그 목록도 그 주소도 쓰지 않기 때문이다.
|
||||
|
||||
<!-- body:end -->
|
||||
|
||||
Reference in New Issue
Block a user