paint-brush
অক্টোপাস এবং টিমসিটির সাথে কীভাবে সিআই/সিডি স্ট্রিমলাইন করবেনদ্বারা@socialdiscoverygroup
4,199 পড়া
4,199 পড়া

অক্টোপাস এবং টিমসিটির সাথে কীভাবে সিআই/সিডি স্ট্রিমলাইন করবেন

দ্বারা Social Discovery Group13m2024/05/14
Read on Terminal Reader

অতিদীর্ঘ; পড়তে

এটি কোনও গোপন বিষয় নয় যে জায়গায় পরিষ্কার এবং সংগঠিত প্রক্রিয়াগুলি ছাড়া, বিকাশকারীরা কার্যকরভাবে সহযোগিতা করতে লড়াই করতে পারে, যার ফলে সফ্টওয়্যার আপডেটগুলি সরবরাহ করতে বিলম্ব হতে পারে। এই নিবন্ধে, সোশ্যাল ডিসকভারি গ্রুপ টিম টিমসিটি এবং অক্টোপাসের মিশ্রণের সাথে কীভাবে একটি সুবিধাজনক এবং নমনীয় CI/CD পাইপলাইন তৈরি করতে হয় তা শেয়ার করে৷
featured image - অক্টোপাস এবং টিমসিটির সাথে কীভাবে সিআই/সিডি স্ট্রিমলাইন করবেন
Social Discovery Group HackerNoon profile picture
0-item


এটি কোনও গোপন বিষয় নয় যে জায়গায় পরিষ্কার এবং সংগঠিত প্রক্রিয়াগুলি ছাড়া, বিকাশকারীরা কার্যকরভাবে সহযোগিতা করতে লড়াই করতে পারে, যার ফলে সফ্টওয়্যার আপডেটগুলি সরবরাহ করতে বিলম্ব হতে পারে। কয়েক বছর আগে, সোশ্যাল ডিসকভারি গ্রুপ টিম একটি সাবঅপ্টিমাল সিআই/সিডি প্রক্রিয়ার চ্যালেঞ্জের মুখোমুখি হয়েছিল। সেই সময়ে, দল টিমসিটি এবং অক্টোপাস ব্যবহার করেছিল, প্রতিটি তার শক্তির সাথে। উদাহরণস্বরূপ, অক্টোপাস স্থাপনার জন্য সুবিধাজনক, যখন TeamCity স্বয়ংক্রিয় পরীক্ষার জন্য ভাল এবং প্রকল্প তৈরির জন্য যথেষ্ট সুবিধাজনক। সর্বাধিক সুবিধাজনক এবং কনফিগারেশনে নমনীয় একটি বিস্তৃত এবং চাক্ষুষভাবে আকর্ষণীয় CI/CD পাইপলাইন তৈরি করতে, সরঞ্জামগুলির মিশ্রণ ব্যবহার করা প্রয়োজন। কোডটি বেশ কয়েকটি প্রকল্পের জন্য বিটবাকেটের একটি স্থানীয় সংগ্রহস্থলে সংরক্ষণ করা হয়েছিল। SDG টিম সমস্যাটি অধ্যয়ন করেছে এবং বিদ্যমান সরঞ্জামগুলি ব্যবহার করে প্রক্রিয়াটিকে অপ্টিমাইজ করার সিদ্ধান্ত নিয়েছে।


মূল অপ্টিমাইজেশান লক্ষ্য:

  1. টিমসিটি থেকে নির্দিষ্ট পরিবেশে স্বয়ংক্রিয় বিল্ড এবং স্থাপনা।
  2. নামকরণ বিল্ড: নামের দ্বারা বিল্ডগুলিকে আলাদা করতে মাস্টার শাখায় "রিলিজ" যোগ করা হয়।
  3. স্বয়ংক্রিয় বিল্ড এবং ডিপ্লোয়মেন্ট যখন সংশ্লিষ্ট পরিষেবাগুলির নিজ নিজ পরীক্ষা পরিবেশে সংশ্লিষ্ট শাখাগুলিতে ঠেলে দেয়।
  4. একটি প্রক্রিয়া প্রতিষ্ঠা করা যেখানে পরীক্ষা এবং উন্নয়ন পরিবেশে স্থাপনা স্টেজিং এবং তারপর উত্পাদনে স্থাপনার আগে অবশ্যই সম্পন্ন করতে হবে। এটি অক্টোপাসে বাস্তবায়িত হয়েছিল।


SDG দলটি বিল্ড এবং স্বয়ংক্রিয় পরীক্ষার জন্য টিমসিটি এবং স্থাপনার জন্য অক্টোপাস ব্যবহার করার সিদ্ধান্ত নিয়েছে।


টিমসিটিতে কী বাস্তবায়িত হয়েছিল:

  1. TeamCity বিনামূল্যে সংস্করণে তিনটি এজেন্ট ব্যবহারের অনুমতি দেয়, যা SDG দলের জন্য যথেষ্ট ছিল। তারা একটি নতুন এজেন্ট ইনস্টল করেছে, এটি পুলে যোগ করেছে এবং এটি তাদের টেমপ্লেটে প্রয়োগ করেছে।

  2. টিমসিটির সর্বশেষ সংস্করণ ব্যবহার করার সময়, দলটি উবুন্টু সার্ভারে কাজ করছিল। স্ক্রিনশট টিম দ্বারা ব্যবহৃত অতিরিক্ত প্লাগইনগুলি দেখায়:



  1. টুলগুলি থেকে, দলটি প্লাগইন যোগ করেছে, যেমন রিপোর্ট তৈরির জন্য Allure 2.14.0 এবং Nuget 5.5.1।
  2. অনুরূপ কার্য সম্পাদন সহজতর করার জন্য, SDG টিম বিভিন্ন ধরনের স্থাপনার জন্য বেশ কিছু টেমপ্লেট তৈরি করেছে: NuGet এবং পরিষেবা।
  3. এই টেমপ্লেটগুলির প্রতিটিতে বেশ কয়েকটি ধাপ অন্তর্ভুক্ত রয়েছে, যা নীচের স্ক্রিনশটগুলিতে প্রতিফলিত হয়েছে৷


NuGet এর জন্য স্থাপনাটি নিম্নরূপ দেখায়:




এটা লক্ষণীয় যে শাখাটি মাস্টার কিনা তার উপর নির্ভর করে, "-release" রিলিজে যোগ করা হয়েছিল (ধাপ 3, 4)।


পরিষেবার জন্য স্থাপনা নীচে দেখা যেতে পারে:


প্রতিটি পরিষেবার জন্য, সিস্টেম ভেরিয়েবলের (পরিষেবার নাম, %build.number%, এবং অন্যান্য) উপর ভিত্তি করে সংশ্লিষ্ট ভেরিয়েবলগুলি প্রতিস্থাপিত হয়েছিল৷


ডকার বিল্ড ধাপের একটি উদাহরণ স্ক্রিনশটে উপস্থাপন করা হয়েছে:


প্রতিটি প্রকল্পের সংগ্রহস্থলে সংশ্লিষ্ট ডকারফাইল রয়েছে।


পদক্ষেপ 4 এবং 5 এর মধ্যে পার্থক্য, পূর্বে উল্লিখিত হিসাবে, নিম্নরূপ ছিল:



%deploymentTarget% ভেরিয়েবল পরিবেশ (গুলি) প্যারামিটার হিসাবে কাজ করে, যেখানে অক্টোপাসের সংশ্লিষ্ট পর্যায়গুলি স্থাপনের সময় পাস করা হয়েছিল (যেমন, পরীক্ষা, দেব)। যখন পরিবর্তনগুলি উন্নয়ন দলের সংশ্লিষ্ট শাখায় (কনফিগার করা) ঠেলে দেওয়া হয়, তখন সংশ্লিষ্ট পরীক্ষার পরিবেশে বিল্ড এবং সফ্টওয়্যার স্থাপনা স্বয়ংক্রিয়ভাবে সঞ্চালিত হয়। সেটিংস নিচের স্ক্রিনশটে দৃশ্যমান। অক্টোপাসের সাথে সংযোগ করতে, দুটি গ্লোবাল প্যারামিটার যোগ করতে হবে: octopus.apiKey এবং octopus.url



উপরন্তু, SDG টিম সংযোগ বিভাগে সমস্ত প্রকল্পের জন্য একটি সাধারণ NuGet সংগ্রহস্থল এবং কন্টেইনার রেজিস্ট্রি সংযুক্ত করেছে।


তদুপরি, SDG ইমেল নোটিফায়ার বিভাগে ইমেল বিজ্ঞপ্তিগুলি কনফিগার করার, ব্যাকআপ বিভাগে ব্যাকআপ সেট আপ করার, প্রয়োজনীয় গোষ্ঠী তৈরি, উপযুক্ত ভূমিকা নির্ধারণ এবং প্রয়োজনীয় গোষ্ঠীতে ব্যবহারকারীদের যুক্ত করার পরামর্শ দেয়। মূল সেটআপ সম্পন্ন হয়েছে, এবং উপসংহারে, টিম নিয়মিতভাবে আপডেটের জন্য পরীক্ষা করার এবং মাসে একবার টিমসিটি আপডেট করার পরামর্শ দেয়।


এরপরে, সোশ্যাল ডিসকভারি গ্রুপ টিম অক্টোপাস কনফিগার করার জন্য এগিয়ে যায়। এই নিবন্ধটি ইনস্টলেশনের বিশদ বিবরণ, মৌলিক ব্যবহারকারীর অধিকার সেটিংস এবং অন্যান্য দিকগুলি বর্ণনা করবে না, কারণ আপনি সহজেই সেগুলি নিজে করতে পারেন৷ দলটি অবিলম্বে লাইব্রেরি বিভাগে কনফিগার করা জীবনচক্রকে সম্বোধন করেছে। নীচের স্ক্রিনশটে, আপনি SDG দলের একটি উদাহরণ প্রবাহ দেখতে পারেন:



তারপর, দলটি পরিবর্তনশীল সেটে থিম দ্বারা প্রয়োজনীয় সমস্ত পরিবর্তনশীল গ্রুপ তৈরি করেছে। প্রতিটি ভেরিয়েবলের জন্য, মান সেট করা হয়েছিল এবং পরিবেশের উপর নির্ভরতা, লক্ষ্য এবং লক্ষ্য ভূমিকা (ট্যাগ) প্রতিষ্ঠিত হয়েছিল। একটি উদাহরণ নীচের স্ক্রিনশটে দেখানো হয়েছে:



Kubernetes-এর ক্লাস্টারগুলি লক্ষ্য হিসাবে কাজ করেছিল, এবং লক্ষ্য ভূমিকাগুলি সংশ্লিষ্ট ক্লাস্টার বা কম্পিউটার পরিবেশের সাথে সংযুক্ত ট্যাগ ছিল। এই সমস্ত অবকাঠামো বিভাগে কনফিগার করা যেতে পারে।


প্রকল্পগুলিকেও গোষ্ঠীভুক্ত করা যেতে পারে, এবং তাদের উপর স্থাপন করা পরিষেবা, পর্যায় এবং সংস্করণগুলি প্রদর্শনের জন্য একটি সুবিধাজনক ড্যাশবোর্ড সেট আপ করা যেতে পারে।

SDG-এর জন্য স্থাপনা প্রক্রিয়াটি নিম্নরূপ দেখায়: সমস্ত পরীক্ষার পর্যায়গুলিকে এক ধাপে একত্রিত করা হয়েছিল, এবং তাদের জন্য একটি সাধারণ টেমপ্লেট তৈরি করা হয়েছিল, একইভাবে স্টেজ এবং লাইভ স্টেজের জন্য।


নীচের স্ক্রিনশটটি দেখায় যে এটি SDG টিমের জন্য কেমন ছিল:

ডানদিকে, পূর্বে বর্ণিত জীবনচক্র নির্বাচন করা হয়েছে। একটি প্যাকেজ স্থাপনের পর্যায়ে মোটামুটি সহজ ডিফল্ট সেটিংস অন্তর্ভুক্ত।

Raw Kubernetes Yaml পর্যায়ে স্থাপনের জন্য, SDG দল সর্বজনীন স্ব-লিখিত Yaml টেমপ্লেট ব্যবহার করেছে। এই উদাহরণে - কুবারনেটস স্ক্রিপ্ট, নীচে আরও বিশদে ব্যাখ্যা করা হয়েছে। লাল রঙে চিহ্নিত সংশ্লিষ্ট পরামিতিগুলিও প্রতিস্থাপিত হয়েছিল। এটি লক্ষণীয় যে প্রয়োজনীয় গ্লোবাল ভেরিয়েবল গ্রুপগুলি ভেরিয়েবল->ভেরিয়েবল সেট মেনুতে সংযুক্ত ছিল এবং প্রকল্প-নির্দিষ্ট ভেরিয়েবলগুলি ভেরিয়েবল->প্রকল্প মেনুতে সেট করা হয়েছিল, যার অগ্রাধিকার ছিল বেশি।


এই নিবন্ধে, SDG টিম প্রকল্পে একটি লোগো যোগ করা, ট্রিগার সেট আপ করা বা অন্যান্য ছোটখাটো বিবরণের মতো বিবরণ এড়িয়ে যাওয়ার সিদ্ধান্ত নিয়েছে। আসুন দুটি গুরুত্বপূর্ণ মেনু আইটেমগুলিতে ফোকাস করি: 1 - রিলিজ, যেখানে আপনি সর্বদা একটি নির্দিষ্ট প্রকাশের সংস্করণ এবং তৈরির তারিখ দেখতে পারেন; এই তথ্যটি প্রজেক্ট ড্যাশবোর্ডেও প্রদর্শিত হয়, 2 - ভেরিয়েবল->প্রিভিউ, যেখানে আপনি দেখতে পারবেন কোন ভেরিয়েবলগুলি সংশ্লিষ্ট পর্যায়ে প্রতিস্থাপিত হবে।





সবচেয়ে গুরুত্বপূর্ণ অংশে যাওয়া - Kubernetes ক্লাস্টারে Yaml টেমপ্লেট স্থাপন। সেগুলি লাইব্রেরি->ধাপ টেমপ্লেট বিভাগে তৈরি করা হয়েছিল। নীচে, SDG দল তাদের প্যারামিটার ব্যবহার করে একটি স্ক্রিনশট উপস্থাপন করেছে। প্রতিটি প্যারামিটারের জন্য, আপনি একটি ট্যাগ, টাইপ এবং ডিফল্ট মান চয়ন করতে পারেন, সেইসাথে একটি বিবরণ যোগ করতে পারেন, যা অত্যন্ত সুপারিশ করা হয়।




এই ক্ষেত্রে কোডটি নিম্নরূপ দেখায়:


 apiVersion: apps/v1 kind: Deployment metadata: name: '#{Octopus.Project.Name | ToLower}' namespace: #{Octopus.Environment.Name | ToLower} labels: Octopus.Kubernetes.DeploymentName: '#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}' spec: replicas: #{Replicas} strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% maxUnavailable: 25% revisionHistoryLimit: 10 progressDeadlineSeconds: 600 selector: matchLabels: Octopus.Kubernetes.DeploymentName: '#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}' template: metadata: labels: Octopus.Kubernetes.DeploymentName: '#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}' spec: volumes: #{if usesidecar} - name: dump-storage persistentVolumeClaim: claimName: dumps-#{Octopus.Environment.Name | ToLower} #{/if} #{if MountFolders} #{each folder in MountFolders} - name: volume-#{folder | ToBase64 | Replace "\W" X | ToLower} hostPath: path: #{folder} type: DirectoryOrCreate #{/each} #{/if} - name: logs-volume hostPath: path: #{LogsDir} type: DirectoryOrCreate - name: appsettings secret: secretName: #{Octopus.Project.Name | ToLower} #{if Secrets} #{each secret in Secrets} - name: #{secret.name} secret: secretName: #{secret.name} #{/each} #{/if} #{if usesidecar} - name: diagnostics emptyDir: {} - name: dumps configMap: name: dumps defaultMode: 511 #{/if} containers: - name: #{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}-container image: #{DockerRegistry}/projectname.#{Octopus.Project.Name | ToLower}:#{Octopus.Release.Notes} #{if resources} resources: #{each resource in resources} #{resource.Key}: #{each entry in resource.Value} #{entry.Key}: #{entry.Value} #{/each} #{/each} #{/if} ports: - name: http containerPort: 80 protocol: TCP env: - value: "Development" name: "ASPNETCORE_ENVIRONMENT" - name: DD_ENV value: "#{Octopus.Environment.Name | ToLower}" - name: DD_SERVICE value: "#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}" - name: DD_VERSION value: "1.0.0" - name: DD_AGENT_HOST value: "#{DatadogAgentHost}" - name: DD_TRACE_ROUTE_TEMPLATE_RESOURCE_NAMES_ENABLED value: "true" - name: DD_RUNTIME_METRICS_ENABLED value: "true" volumeMounts: #{if usesidecar} - name: dump-storage mountPath: /tmp/dumps #{/if} #{if MountFolders} #{each folder in MountFolders} - mountPath: #{folder} name: volume-#{folder | ToBase64 | Replace "\W" X | ToLower} #{/each} #{/if} - mountPath: #{LogsDir} name: logs-volume #{if usesidecar} - name: diagnostics mountPath: /tmp #{/if} - name: appsettings readOnly: true mountPath: /app/appsettings.json subPath: appsettings.json #{if Secrets} #{each secret in Secrets} - name: #{secret.name} readOnly: true mountPath: #{secret.mountPath} subPath: #{secret.subPath} #{/each} #{/if} readinessProbe: httpGet: path: hc port: http scheme: HTTP initialDelaySeconds: #{InitialDelaySeconds} imagePullPolicy: IfNotPresent securityContext: {} #{if usesidecar} - name: sidecar image: '#{DockerRegistry}/monitor:3' command: - /bin/sh args: - '-c' - while true; do . /app/init.sh; sleep 1m;done env: - name: USE_MEMORY value: '2048' - name: PROJECT value: "#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}" resources: {} volumeMounts: - name: diagnostics mountPath: /tmp - name: dump-storage mountPath: /tmp/dumps - name: dumps mountPath: /app/init.sh subPath: init.sh shareProcessNamespace: true #{/if} affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: environment operator: In values: - "#{Node}" --- apiVersion: v1 kind: Service metadata: name: #{Octopus.Project.Name | ToLower} namespace: #{Octopus.Environment.Name | ToLower} labels: Octopus.Kubernetes.DeploymentName: '#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}' spec: type: ClusterIP selector: Octopus.Kubernetes.DeploymentName: '#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}' ports: - name: http port: 80 targetPort: http protocol: TCP --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: ingress.kubernetes.io/ssl-redirect: 'false' nginx.ingress.kubernetes.io/ssl-redirect: 'false' cert-manager.io/cluster-issuer: "letsencrypt-cluster-issuer" cert-manager.io/renew-before: '#{LetsencryptRenewBefore}' kubernetes.io/ingress.class: nginx #{if IngressAnnotations} #{each annotation in IngressAnnotations} #{annotation.Key}: #{annotation.Value} #{/each} #{/if} name: #{Octopus.Project.Name | ToLower} namespace: #{Octopus.Environment.Name | ToLower} labels: Octopus.Kubernetes.DeploymentName: '#{Octopus.Project.Name | ToLower}-#{Octopus.Environment.Name | ToLower}' spec: tls: #{if ExternalHost} #{each host in ExternalHost} - hosts: - #{host} secretName: #{Octopus.Project.Name | ToLower}-#{host | ToBase64 | Replace "\W" X | ToLower}-tls #{/each} #{/if} rules: #{if ExternalHost} #{each host in ExternalHost} - host: '#{host}' http: paths: - path: / pathType: ImplementationSpecific backend: service: name: #{Octopus.Project.Name | ToLower} port: name: http #{/each} #{/if} #{if usesidecar} --- apiVersion: v1 kind: ConfigMap metadata: name: dumps namespace: #{Octopus.Environment.Name | ToLower} data: init.sh: |- #!/usr/bin/env bash mem=$(ps aux | awk '{print $6}' | sort -rn | head -1) mb=$(($mem/1024)) archiveDumpPath="/tmp/dumps/$PROJECT-$(date +"%Y%m%d%H%M%S").zip" fullPathGc="/tmp/$PROJECT-$(date +"%Y%m%d%H%M%S").dump" echo "mem:" $mb" project:" $PROJECT "use:" $USE_MEMORY if [ "$mb" -gt "$USE_MEMORY" ]; then export USE_MEMORY=$(($USE_MEMORY*2)) pid=$(dotnet-dump ps | awk '{print $1}') dotnet-dump collect -p $pid -o $fullPathGc zip $fullPathGc.zip $fullPathGc mv $fullPathGc.zip $archiveDumpPath rm $fullPathGc fi #{/if}


অক্টোপাসের সমস্ত ভেরিয়েবল কোডে নিম্নলিখিত বিন্যাসে নির্দিষ্ট করা হয়েছে: '#{Octopus.Project.Name | ToLower}' , যেখানে শেষ অংশটি ছোট হাতের অক্ষরে রূপান্তর নির্দেশ করে।


শেষ কনফিগারেশন ফাইলটি তৈরি করা হয়েছিল স্বয়ংক্রিয়ভাবে .NET পরিষেবাগুলির অবস্থা সংরক্ষণ করার জন্য যখন তারা একটি নির্দিষ্ট মেমরি ব্যবহারের সীমাতে পৌঁছেছে। এটি উল্লেখযোগ্যভাবে বিকাশের সময় মেমরি ফাঁস সনাক্ত করতে এবং পরিষেবাগুলি অবিলম্বে ঠিক করতে সহায়তা করে।


অবশেষে, পরিষেবা ড্যাশবোর্ডটি নিম্নরূপ দেখায়:


ডেভেলপমেন্ট এবং টেস্টিং দলগুলি এই ড্যাশবোর্ডের সাথে কাজ করা খুব সুবিধাজনক বলে মনে করেছে।


অপ্টিমাইজেশন ফলাফল:


  1. SDG টিম একটি দক্ষ CI/CD প্রক্রিয়া তৈরি করেছে যা উন্নয়নের গতি এবং সুবিধার উল্লেখযোগ্যভাবে উন্নতি করেছে। দলটি এই প্রক্রিয়া কাঠামোর মধ্যে বেশ দীর্ঘ কাজ করেছে।
  2. SDG সুবিধাজনক TeamCity টুল এবং স্বয়ংক্রিয় পরিষেবা স্থাপনায় স্বয়ংক্রিয় পরীক্ষাও চালু করেছে।
  3. ব্যবহারকারী-বান্ধব অক্টোপাস ড্যাশবোর্ডের মাধ্যমে, দলটি অ্যাক্সেসের অধিকারগুলি কনফিগার করেছে এবং স্থাপনা এবং পরিবেশগুলি পরিচালনা করতে পারে।

পরবর্তীকালে, এসডিজি অক্টোপাসে আরও অনেক বৈশিষ্ট্য বাস্তবায়ন করে। উদাহরণস্বরূপ, একটি সময়সূচীতে রাতে ক্লাস্টারগুলির স্বয়ংক্রিয় শাটডাউন।


যাইহোক, পরিপূর্ণতার সাধনা কোন সীমানা জানে না। সোশ্যাল ডিসকভারি গ্রুপ টিম Azure DevOps আয়ত্ত করে তাদের উন্নয়নকে এগিয়ে নিয়েছে। তারা হেলমে একটি ইকোসিস্টেমের মধ্যে Azure DevOps-এ একটি প্রক্রিয়া সেট আপ করেছে, এমনকি আরও ব্যাপক এবং দক্ষ। এটি পরবর্তী নিবন্ধে কভার করা হবে।


অক্টোপাস এবং টিমসিটি ব্যবহার করে সিআই/সিডি সেট আপ এবং অপ্টিমাইজ করার আপনার অভিজ্ঞতা সম্পর্কে আমরা শুনতে চাই। আপনার অন্তর্দৃষ্টি এবং টিপস শেয়ার করুন!

L O A D I N G
. . . comments & more!

About Author

Social Discovery Group HackerNoon profile picture
Social Discovery Group@socialdiscoverygroup
We solve the problem of loneliness, isolation, and disconnection with the help of digital reality.

আসে ট্যাগ

এই নিবন্ধটি উপস্থাপন করা হয়েছে...