17 min read #devops #runbooks
On this page

wildwindstudio.com EdgeOne (EO) Operations Manual

Site zone: wildwindstudio.com = zone-31ojren22baa (Tencent Cloud EO, Personal plan, dnsPodAccess integration, DNS records managed in DNSPod) Covers two types of operations: ① Reverse proxy backend API domain into EO; ② Host frontend static site using a COS bucket + EO.


0. Credential Map (Sanitized)

CredentialUsageStorage Location
Primary Account Key (appid 1258895932, labeled tencent_sms)Manages EO / DNSPod / COS / CAM; used for all management operations in this manualProject resflow/config.toml under [tencent_sms] (do not commit to repo)
tencent_email KeyAnother account (appid 1392608203), unrelated to this zoneSame [tencent_email] section
Frontend Upload CredentialCAM Sub-user cos_op (Uin 100049168554), for frontend coscli uploads + EO cache purgingHeld on the frontend side; policy see §4
CLB Origin Certificate (certbot)Certificate for the EO→CLB origin fetch segmentbj-middleware-1, see another manual "wildwindstudio.com Certificate Auto-Renewal"

⚠️ tccli calls to teo for write operations must include --endpoint teo.tencentcloudapi.com, otherwise it defaults to the international site teo.intl.tencentcloudapi.com causing connection timeouts. ⚠️ tccli does not have a cos service, and the machine lacks coscli/SDK; COS operations use hand-written HMAC-SHA1 signatures + python requests to directly call the XML API (script see §5).

Set management credentials to environment variables (subsequent commands assume they are exported):

# Read from config.toml's [tencent_sms] to avoid pasting plaintext
cd <resflow project directory>
eval "$(python3 - <<'PY'
import re
t=open('config.toml').read()
m=re.search(r'\[tencent_sms\](.*?)(\n\[|\Z)', t, re.S).group(1)
sid=re.search(r'secret_id\s*=\s*"([^"]+)"',m).group(1)
sk=re.search(r'secret_key\s*=\s*"([^"]+)"',m).group(1)
print(f'export TENCENTCLOUD_SECRET_ID={sid}; export TENCENTCLOUD_SECRET_KEY={sk}')
PY
)"

1. Scenario A: Reverse Proxy Backend API Domain into EO

Using cn-reserve-api.wildwindstudio.com as an example (origin fetch to Tencent Cloud CLB).

Scenario A: API Reverse Proxy Request Chain Client HTTPS EO Edge TLS Termination · CC/WAF Basic Protection Origin Fetch HTTPS:443 HostHeader Pass-through of Original Domain CLB lb-mm70ra69 443 Listener · L7 Host Rules k8s Two HTTPS segments: Client→EO Edge certificate termination, EO→CLB origin fetch remains HTTPS:443 (origin certificate renewed by certbot), HostHeader pass-through allows CLB to forward to k8s based on domain; if new host has no independent policy, it defaults to site-level ZoneDefaultPolicy protection.

Steps (Order: Create origin fetch + certificate first, wait for certificate to be deployed before switching DNS, to avoid 5xx downtime):

ZONE=zone-31ojren22baa
DOMAIN=cn-reserve-api.wildwindstudio.com
CLB=lb-mm70ra69-ihl1vfoqucp93sma.clb.bj-tencentclb.com

# ① Create acceleration domain (IP_DOMAIN origin fetch to CLB, Force HTTPS:443; CLB's 80 is a forced 302 redirect, do not origin fetch to 80)
tccli teo CreateAccelerationDomain --region ap-guangzhou --endpoint teo.tencentcloudapi.com \
  --ZoneId $ZONE --DomainName $DOMAIN \
  --OriginInfo '{"OriginType":"IP_DOMAIN","Origin":"'$CLB'","PrivateAccess":"off"}' \
  --OriginProtocol HTTPS --HttpsOriginPort 443
# Returns OwnershipVerification=null, meaning no ownership verification is required (subdomain inherits from zone)

# ② Enable edge free certificate (EO auto-signs + auto-renews)
tccli teo ModifyHostsCertificate --region ap-guangzhou --endpoint teo.tencentcloudapi.com \
  --ZoneId $ZONE --Hosts '["'$DOMAIN'"]' --Mode eofreecert

# Poll until certStatus=deployed (approx. 4~10 minutes)
tccli teo DescribeAccelerationDomains --region ap-guangzhou --endpoint teo.tencentcloudapi.com \
  --ZoneId $ZONE | python3 -c "import json,sys;d=json.load(sys.stdin);[print(a['DomainStatus'],(a.get('Certificate') or {}).get('List',[{}])[0].get('Status')) for a in d['AccelerationDomains'] if a['DomainName']=='$DOMAIN']"

# ③ After certificate is deployed, change the DNSPod record for this domain to point to the EO entry <domain>.eo.dnse1.com
#    —— Find RecordId then use ModifyRecord (only change this one record)
tccli dnspod DescribeRecordList --endpoint dnspod.tencentcloudapi.com \
  --Domain wildwindstudio.com --Subdomain cn-reserve-api           # Get RecordId
tccli dnspod ModifyRecord --endpoint dnspod.tencentcloudapi.com \
  --Domain wildwindstudio.com --RecordId <RECORD_ID> \
  --SubDomain cn-reserve-api --RecordType CNAME --RecordLine Default \
  --Value cn-reserve-api.wildwindstudio.com.eo.dnse1.com --TTL 600

Verification (Pre-check using EO edge IP before switching DNS):

IP=$(dig +short @119.29.29.29 cn-reserve-api.wildwindstudio.com.eo.dnse1.com A | grep -E '^[0-9]' | head -1)
curl -s -o /dev/null -w "HTTP %{http_code} TLS %{ssl_verify_result}\n" \
  --resolve cn-reserve-api.wildwindstudio.com:443:$IP https://cn-reserve-api.wildwindstudio.com/openapi

Protection: When new hosts have no independent domain-level policy, it falls back to the site-level ZoneDefaultPolicy (CC adaptive rate limiting on/Loose/Challenge + Managed WAF on/DetectionOnly). This is basic protection, no fine-grained rate limiting configured. View: tccli teo DescribeSecurityPolicy ... --Entity ZoneDefaultPolicy.

Note: The CC action is JSChallenge; non-browser clients (e.g., game clients connecting directly to the API) cannot solve the challenge; under Loose mode, it only triggers in extreme anomalies, which is acceptable.


2. Scenario B: COS Static Frontend + EO Hosting

Using cn-reservation.wildwindstudio.com as an example, bucket cn-reservation-webui-1258895932 (ap-nanjing). Refer to bucket game-reserve-webui-1258895932 (ap-tokyo, reservation site).

Scenario B: COS Static Frontend Request Chain Client HTTPS EO Edge Origin Fetch (FOLLOW) COS Static Website Endpoint <bucket>.cos-website.<region>.myqcloud.com OriginProtocol=FOLLOW, origin fetch to COS static website endpoint (bucket-website domain); unlike Scenario A, COS origin cannot pass HostHeader, EO fills it automatically, manually specifying it causes an error.

Steps:

# ① Create bucket (public read) + ② Enable static website (index.html + AutoAddressing)
#    —— Use §5's cos_admin.py (hand-written signature), or console operation
python3 cos_admin.py create-bucket cn-reservation-webui-1258895932 ap-nanjing

# ③ EO Create acceleration domain (COS origin fetch; when OriginType=COS, [cannot] pass HostHeader, EO fills it automatically)
ZONE=zone-31ojren22baa
WEP=cn-reservation-webui-1258895932.cos-website.ap-nanjing.myqcloud.com
tccli teo CreateAccelerationDomain --region ap-guangzhou --endpoint teo.tencentcloudapi.com \
  --ZoneId $ZONE --DomainName cn-reservation.wildwindstudio.com \
  --OriginInfo '{"OriginType":"COS","Origin":"'$WEP'","PrivateAccess":"off"}' \
  --OriginProtocol FOLLOW --HttpOriginPort 80 --HttpsOriginPort 443

# ④ Enable edge certificate (same as Scenario A ②)
tccli teo ModifyHostsCertificate --region ap-guangzhou --endpoint teo.tencentcloudapi.com \
  --ZoneId $ZONE --Hosts '["cn-reservation.wildwindstudio.com"]' --Mode eofreecert

# ⑤ DNS: New subdomain uses CreateRecord (do not touch any existing records)
tccli dnspod CreateRecord --endpoint dnspod.tencentcloudapi.com \
  --Domain wildwindstudio.com --SubDomain cn-reservation \
  --RecordType CNAME --RecordLine Default \
  --Value cn-reservation.wildwindstudio.com.eo.dnse1.com --TTL 600

Verification (Empty bucket origin fetch should return COS 404, proving the chain works; once index.html is uploaded, it returns 200):

DOM=cn-reservation.wildwindstudio.com
IP=$(dig +short @119.29.29.29 $DOM A | grep -E '^[0-9]' | head -1)
echo | openssl s_client -connect $IP:443 -servername $DOM 2>/dev/null | openssl x509 -noout -subject
# subject=CN=<domain> means edge certificate is deployed (if not deployed, it first serves *.cdn.myqcloud.com default certificate, TLS handshake fails)
curl -s -o /dev/null -w "HTTP %{http_code}\n" --resolve $DOM:443:$IP https://$DOM/

3. Frontend Deployment (coscli Upload + EO Cache Purge)

Frontend pair (one set per site, just change bucket / change purge target domain):

coscli Configuration (cn-reservation-coscli.yaml):

cos:
  base:
    secretid: <cos_op's SecretId, sanitized>
    secretkey: <cos_op's SecretKey, sanitized>
    sessiontoken: ""
    protocol: https
  buckets:
  - name: cn-reservation-webui-1258895932     # Change to new bucket
    alias: website
    region: ap-nanjing                          # Change region
    endpoint: cos.ap-nanjing.myqcloud.com       # Change endpoint
    ofs: false

Cache Purge Script (TC3 signature calls teo CreatePurgeTask, only change Targets domain for new sites):

export TENCENTCLOUD_SECRET_ID=<cos_op SecretId>
export TENCENTCLOUD_SECRET_KEY=<cos_op SecretKey>
# Change Targets in payload to target domain:
# {"ZoneId":"*","Type":"purge_host","Targets":["cn-reservation.wildwindstudio.com"]}
./cn-reservation-purge.sh

Release process: coscli sync <build artifact directory> cos://website/ -c cn-reservation-coscli.yaml → Run cache purge script.


4. Frontend Upload Credential CAM Authorization (Required for New Buckets)

The permissions for the frontend sub-user cos_op are controlled by a single custom policy cos_full_permission (PolicyId 274095472). For every new bucket added, you must add the new bucket resource to the cos:* statement of this policy, otherwise coscli upload will report 403 AccessDenied. (teo:CreatePurgeTask is already authorized for *, no need to modify for purging any domain.)

# Read current policy
tccli cam GetPolicy --PolicyId 274095472

# Update (keep old bucket resources, append new bucket resources; resource format qcs::cos:<region>:uid/<appid>:<bucket-appid>/*)
tccli cam UpdatePolicy --PolicyId 274095472 --PolicyDocument '{
  "version":"2.0",
  "statement":[
    {"action":["cos:*"],"effect":"allow","resource":[
      "qcs::cos:ap-tokyo:uid/1258895932:game-reserve-webui-1258895932/*",
      "qcs::cos:ap-nanjing:uid/1258895932:cn-reservation-webui-1258895932/*"
    ]},
    {"action":["teo:CreatePurgeTask"],"effect":"allow","resource":["*"]}
  ]
}'

Locate sub-user: cam ListUsers → Match AK against candidate cam ListAccessKeys --TargetUin <uin>cam ListAttachedUserPolicies --TargetUin <uin> to find policy.


5. Appendix: COS Management Script (Hand-written Signature, No coscli Needed)

When coscli/SDK is not available on the machine, use python requests + COS HMAC-SHA1 signature to directly call the XML API. Core signature function:

import time, hmac, hashlib, requests
def cos_sign(SID, SKEY, method, host, uri='/', headers=None, params=None):
    headers=headers or {'host':host}; params=params or {}
    now=int(time.time()); kt=f"{now};{now+600}"
    sk=hmac.new(SKEY.encode(),kt.encode(),hashlib.sha1).hexdigest()
    hl=';'.join(sorted(k.lower() for k in headers)); pl=';'.join(sorted(k.lower() for k in params))
    enc=lambda d:'&'.join(f"{k.lower()}={requests.utils.quote(str(d[k]),safe='')}" for k in sorted(d,key=str.lower))
    fmt=f"{method.lower()}\n{uri}\n{enc(params)}\n{enc(headers)}\n"
    sts=f"sha1\n{kt}\n{hashlib.sha1(fmt.encode()).hexdigest()}\n"
    sig=hmac.new(sk.encode(),sts.encode(),hashlib.sha1).hexdigest()
    return (f"q-sign-algorithm=sha1&q-ak={SID}&q-sign-time={kt}&q-key-time={kt}"
            f"&q-header-list={hl}&q-url-param-list={pl}&q-signature={sig}")
  • GET Service (List all buckets): host service.cos.myqcloud.com, GET /
  • PUT Bucket + Public Read: host <bucket>.cos.<region>.myqcloud.com, PUT /, sign with x-cos-acl: public-read
  • PUT Static Website: PUT /?website, body see below, requires content-md5 (base64(md5(body))) + content-type: application/xml
  • Verify: GET /?acl, GET /?website

Static Website Body (consistent with reference bucket):

<WebsiteConfiguration>
  <IndexDocument><Suffix>index.html</Suffix></IndexDocument>
  <AutoAddressing><Status>Enabled</Status></AutoAddressing>
</WebsiteConfiguration>

6. Common Pitfalls Quick Reference

SymptomCause / Handling
teo.intl... timed outtccli teo write operation missing --endpoint teo.tencentcloudapi.com
UnSupportChangeHostHeaderWithThisOriginTypeCannot pass HostHeader when OriginType=COS, remove it (EO fills it automatically)
TLS handshake failure after DNS switch, HTTP 000Edge certificate not deployed yet, edge first serves *.cdn.myqcloud.com; wait for certStatus=deployed and edge subject=own domain (minutes to tens of minutes)
coscli upload 403 AccessDeniedcos_op policy missing bucket resource, see §4
DomainStatus long processEO acceleration domain provisioning in progress, does not affect deployed certificate functionality; will automatically flip to online
DescribeDnsRecords(teo) returns 0 recordsdnsPodAccess mode DNS records are in DNSPod, use dnspod DescribeRecordList to query

Appendix: Existing Acceleration Domains in This Zone

DomainOrigin FetchUsage
cn-reserve-api.wildwindstudio.comCLB lb-mm70ra69 (HTTPS:443)CN Backend API
cn-reservation.wildwindstudio.comCOS cn-reservation-webui (ap-nanjing)CN Reservation Frontend
reservation / meltdown / wwwCOS Static BucketsJP/Official Website Frontend